portacode 1.4.12.dev8__tar.gz → 1.4.15.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/PKG-INFO +1 -1
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/_version.py +2 -2
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +12 -3
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/proxmox_infra.py +309 -49
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/system_handlers.py +131 -2
- portacode-1.4.15.dev0/portacode/connection/handlers/test_proxmox_infra.py +13 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/SOURCES.txt +1 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.claude/settings.local.json +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.gitignore +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.gitmodules +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/LICENSE +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/MANIFEST.in +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/Makefile +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/backup.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/connect.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/connect.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docker-compose.yaml +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/pair-device-button.png +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/pairing-request.png +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/student-workspace.png +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/__main__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/cli.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/client.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/terminal.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/data.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/keypair.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/logging_categories.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/pairing.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/service.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/pyproject.toml +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/restore.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/run_tests.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/setup.cfg +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/setup.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_device_online.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_file_operations.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_login_flow.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_request_id.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/.env.example +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/README.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/cli.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/__init__.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/base_test.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/runner.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/requirements.txt +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/agent_context_management.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/django_server_time_sync.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/pairing_tester.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/validate.sh +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
31
|
+
__version__ = version = '1.4.15.dev0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 15, 'dev0')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
{portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -326,9 +326,10 @@ Configures a Proxmox node for Portacode infrastructure usage (API token validati
|
|
|
326
326
|
|
|
327
327
|
**Payload Fields:**
|
|
328
328
|
|
|
329
|
-
* `token_identifier` (string,
|
|
330
|
-
* `token_value` (string,
|
|
331
|
-
* `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`.
|
|
329
|
+
* `token_identifier` (string, optional when reconfiguring): API token identifier in the form `user@realm!tokenid`. Required on first configuration or when replacing the stored token.
|
|
330
|
+
* `token_value` (string, optional when reconfiguring): Secret value associated with the token. Required when `token_identifier` is supplied.
|
|
331
|
+
* `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`. When omitted, the last configured value is preserved.
|
|
332
|
+
* `cloudflare_api_token` (string, optional): Cloudflare API token the host can reuse later to provision tunnels.
|
|
332
333
|
|
|
333
334
|
**Responses:**
|
|
334
335
|
|
|
@@ -361,6 +362,9 @@ Creates a Portacode-managed LXC container, starts it, and bootstraps the Portaco
|
|
|
361
362
|
* `username` (string, optional): OS user to provision (defaults to `svcuser`).
|
|
362
363
|
* `password` (string, optional): Password for the user (used only during provisioning).
|
|
363
364
|
* `ssh_key` (string, optional): SSH public key to add to the user.
|
|
365
|
+
* `device_id` (string, optional): ID of the Device record that already exists on the dashboard.
|
|
366
|
+
* `device_public_key` (string, optional): PEM-encoded Portacode public key. When supplied together with `device_private_key` the handler injects the keypair, records the device metadata, and runs `portacode service install` automatically.
|
|
367
|
+
* `device_private_key` (string, optional): PEM-encoded private key that pairs with `device_public_key`. Both key fields must be present for the automatic service-install mode.
|
|
364
368
|
|
|
365
369
|
**Responses:**
|
|
366
370
|
|
|
@@ -418,6 +422,8 @@ Emitted after a successful `create_proxmox_container` action. Contains the new c
|
|
|
418
422
|
* `public_key` (string): Portacode public auth key created inside the new container.
|
|
419
423
|
* `container` (object): Metadata such as `vmid`, `hostname`, `template`, `storage`, `disk_gib`, `ram_mib`, and `cpus`.
|
|
420
424
|
* `setup_steps` (array[object]): Detailed bootstrap step results (name, stdout/stderr, elapsed time, and status).
|
|
425
|
+
* `device_id` (string, optional): Mirrors the `device_id` supplied with `create_proxmox_container`, if any.
|
|
426
|
+
* `service_installed` (boolean): True when the handler already ran `portacode service install` (with a provided keypair); otherwise it remains False and the dashboard can call `start_portacode_service`.
|
|
421
427
|
|
|
422
428
|
### `proxmox_container_progress`
|
|
423
429
|
|
|
@@ -450,6 +456,7 @@ Runs `sudo portacode service install` inside the container after the dashboard h
|
|
|
450
456
|
* Emits additional [`proxmox_container_progress`](#proxmox_container_progress-event) events to report the authentication and service-install steps.
|
|
451
457
|
* On success, emits a [`proxmox_service_started`](#proxmox_service_started-event).
|
|
452
458
|
* On failure, emits a generic [`error`](#error) event.
|
|
459
|
+
* When `create_proxmox_container` already provided a dashboard-generated keypair, the handler may have installed the service already, so this call is optional unless you need to re-run the install.
|
|
453
460
|
|
|
454
461
|
### `proxmox_service_started`
|
|
455
462
|
|
|
@@ -1120,6 +1127,8 @@ Provides system information in response to a `system_info` action. Handled by [`
|
|
|
1120
1127
|
* `message` (string|null): Informational text about the network setup attempt.
|
|
1121
1128
|
* `bridge` (string): The bridge interface configured (typically `vmbr1`).
|
|
1122
1129
|
* `health` (string|null): `"healthy"` when the connectivity verification succeeded.
|
|
1130
|
+
* `cloudflare` (object): Optional Cloudflare metadata collected for future tunnels:
|
|
1131
|
+
* `configured` (boolean): True when a Cloudflare API token is stored.
|
|
1123
1132
|
* `node_status` (object|null): Status response returned by the Proxmox API when validating the token.
|
|
1124
1133
|
* `managed_containers` (object): Cached summary of the Portacode-managed containers:
|
|
1125
1134
|
* `updated_at` (string): ISO timestamp when this snapshot was last refreshed.
|
{portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/proxmox_infra.py
RENAMED
|
@@ -7,15 +7,17 @@ import json
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
9
|
import secrets
|
|
10
|
+
import shlex
|
|
10
11
|
import shutil
|
|
11
12
|
import stat
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
15
|
+
import tempfile
|
|
14
16
|
import time
|
|
15
17
|
import threading
|
|
16
18
|
from datetime import datetime
|
|
17
19
|
from pathlib import Path
|
|
18
|
-
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
|
20
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
19
21
|
|
|
20
22
|
import platformdirs
|
|
21
23
|
|
|
@@ -261,7 +263,10 @@ def _ensure_bridge(bridge: str = DEFAULT_BRIDGE) -> Dict[str, Any]:
|
|
|
261
263
|
apt = shutil.which("apt-get")
|
|
262
264
|
if not apt:
|
|
263
265
|
raise RuntimeError("dnsmasq is missing and apt-get unavailable to install it")
|
|
264
|
-
_call_subprocess([apt, "update"], check=
|
|
266
|
+
update = _call_subprocess([apt, "update"], check=False)
|
|
267
|
+
if update.returncode not in (0, 100):
|
|
268
|
+
msg = update.stderr or update.stdout or f"exit status {update.returncode}"
|
|
269
|
+
raise RuntimeError(f"apt-get update failed: {msg}")
|
|
265
270
|
_call_subprocess([apt, "install", "-y", "dnsmasq"], check=True)
|
|
266
271
|
_write_bridge_config(bridge)
|
|
267
272
|
_ensure_sysctl()
|
|
@@ -428,7 +433,12 @@ def _friendly_step_label(step_name: str) -> str:
|
|
|
428
433
|
return normalized.capitalize()
|
|
429
434
|
|
|
430
435
|
|
|
431
|
-
def _build_bootstrap_steps(
|
|
436
|
+
def _build_bootstrap_steps(
|
|
437
|
+
user: str,
|
|
438
|
+
password: str,
|
|
439
|
+
ssh_key: str,
|
|
440
|
+
include_portacode_connect: bool = True,
|
|
441
|
+
) -> List[Dict[str, Any]]:
|
|
432
442
|
steps = [
|
|
433
443
|
{
|
|
434
444
|
"name": "apt_update",
|
|
@@ -465,11 +475,14 @@ def _build_bootstrap_steps(user: str, password: str, ssh_key: str) -> List[Dict[
|
|
|
465
475
|
"cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
|
|
466
476
|
"retries": 0,
|
|
467
477
|
})
|
|
468
|
-
steps.extend(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
478
|
+
steps.extend(
|
|
479
|
+
[
|
|
480
|
+
{"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
|
|
481
|
+
{"name": "install_portacode", "cmd": "python3 -m pip install --upgrade portacode", "retries": 0},
|
|
482
|
+
]
|
|
483
|
+
)
|
|
484
|
+
if include_portacode_connect:
|
|
485
|
+
steps.append({"name": "portacode_connect", "type": "portacode_connect", "timeout_s": 30})
|
|
473
486
|
return steps
|
|
474
487
|
|
|
475
488
|
|
|
@@ -679,6 +692,74 @@ def _run_pct_check(vmid: int, cmd: str) -> Dict[str, Any]:
|
|
|
679
692
|
return res
|
|
680
693
|
|
|
681
694
|
|
|
695
|
+
def _run_pct_exec(vmid: int, command: Sequence[str]) -> subprocess.CompletedProcess[str]:
|
|
696
|
+
return _call_subprocess(["pct", "exec", str(vmid), "--", *command])
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _run_pct_exec_check(vmid: int, command: Sequence[str]) -> subprocess.CompletedProcess[str]:
|
|
700
|
+
res = _run_pct_exec(vmid, command)
|
|
701
|
+
if res.returncode != 0:
|
|
702
|
+
raise RuntimeError(res.stderr or res.stdout or f"pct exec {' '.join(command)} failed")
|
|
703
|
+
return res
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _run_pct_push(vmid: int, src: str, dest: str) -> subprocess.CompletedProcess[str]:
|
|
707
|
+
return _call_subprocess(["pct", "push", str(vmid), src, dest])
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def _push_bytes_to_container(
|
|
711
|
+
vmid: int, user: str, path: str, data: bytes, mode: int = 0o600
|
|
712
|
+
) -> None:
|
|
713
|
+
logger.debug("Preparing to push %d bytes to container vmid=%s path=%s for user=%s", len(data), vmid, path, user)
|
|
714
|
+
tmp_path: Optional[str] = None
|
|
715
|
+
try:
|
|
716
|
+
parent = Path(path).parent
|
|
717
|
+
parent_str = parent.as_posix()
|
|
718
|
+
if parent_str not in {"", ".", "/"}:
|
|
719
|
+
_run_pct_exec_check(vmid, ["mkdir", "-p", parent_str])
|
|
720
|
+
_run_pct_exec_check(vmid, ["chown", "-R", f"{user}:{user}", parent_str])
|
|
721
|
+
|
|
722
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
723
|
+
tmp.write(data)
|
|
724
|
+
tmp.flush()
|
|
725
|
+
os.fsync(tmp.fileno())
|
|
726
|
+
tmp_path = tmp.name
|
|
727
|
+
|
|
728
|
+
push_res = _run_pct_push(vmid, tmp_path, path)
|
|
729
|
+
if push_res.returncode != 0:
|
|
730
|
+
raise RuntimeError(push_res.stderr or push_res.stdout or f"pct push returned {push_res.returncode}")
|
|
731
|
+
|
|
732
|
+
_run_pct_exec_check(vmid, ["chown", f"{user}:{user}", path])
|
|
733
|
+
_run_pct_exec_check(vmid, ["chmod", format(mode, "o"), path])
|
|
734
|
+
logger.debug("Successfully pushed %d bytes to vmid=%s path=%s", len(data), vmid, path)
|
|
735
|
+
except Exception as exc:
|
|
736
|
+
logger.error("Failed to write to container vmid=%s path=%s for user=%s: %s", vmid, path, user, exc)
|
|
737
|
+
raise
|
|
738
|
+
finally:
|
|
739
|
+
if tmp_path:
|
|
740
|
+
try:
|
|
741
|
+
os.remove(tmp_path)
|
|
742
|
+
except OSError as cleanup_exc:
|
|
743
|
+
logger.warning("Failed to remove temporary file %s: %s", tmp_path, cleanup_exc)
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _resolve_portacode_key_dir(vmid: int, user: str) -> str:
|
|
747
|
+
data_dir_cmd = f"su - {user} -c 'echo -n ${{XDG_DATA_HOME:-$HOME/.local/share}}'"
|
|
748
|
+
data_home = _run_pct_check(vmid, data_dir_cmd)["stdout"].strip()
|
|
749
|
+
portacode_dir = f"{data_home}/portacode"
|
|
750
|
+
_run_pct_exec_check(vmid, ["mkdir", "-p", portacode_dir])
|
|
751
|
+
_run_pct_exec_check(vmid, ["chown", "-R", f"{user}:{user}", portacode_dir])
|
|
752
|
+
return f"{portacode_dir}/keys"
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _deploy_device_keypair(vmid: int, user: str, private_key: str, public_key: str) -> None:
|
|
756
|
+
key_dir = _resolve_portacode_key_dir(vmid, user)
|
|
757
|
+
priv_path = f"{key_dir}/id_portacode"
|
|
758
|
+
pub_path = f"{key_dir}/id_portacode.pub"
|
|
759
|
+
_push_bytes_to_container(vmid, user, priv_path, private_key.encode(), mode=0o600)
|
|
760
|
+
_push_bytes_to_container(vmid, user, pub_path, public_key.encode(), mode=0o644)
|
|
761
|
+
|
|
762
|
+
|
|
682
763
|
def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -> Dict[str, Any]:
|
|
683
764
|
cmd = ["pct", "exec", str(vmid), "--", "bash", "-lc", f"su - {user} -c 'portacode connect'"]
|
|
684
765
|
proc = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
@@ -704,9 +785,12 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
|
|
|
704
785
|
stable = 0
|
|
705
786
|
history: List[Dict[str, Any]] = []
|
|
706
787
|
|
|
788
|
+
process_exited = False
|
|
789
|
+
exit_out = exit_err = ""
|
|
707
790
|
while time.time() - start < timeout_s:
|
|
708
791
|
if proc.poll() is not None:
|
|
709
|
-
|
|
792
|
+
process_exited = True
|
|
793
|
+
exit_out, exit_err = proc.communicate(timeout=1)
|
|
710
794
|
history.append(
|
|
711
795
|
{
|
|
712
796
|
"timestamp_s": round(time.time() - start, 2),
|
|
@@ -714,13 +798,7 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
|
|
|
714
798
|
"returncode": proc.returncode,
|
|
715
799
|
}
|
|
716
800
|
)
|
|
717
|
-
|
|
718
|
-
"ok": False,
|
|
719
|
-
"error": "portacode connect exited before keys were created",
|
|
720
|
-
"stdout": (out or "").strip(),
|
|
721
|
-
"stderr": (err or "").strip(),
|
|
722
|
-
"history": history,
|
|
723
|
-
}
|
|
801
|
+
break
|
|
724
802
|
pub_size = file_size(pub_path)
|
|
725
803
|
priv_size = file_size(priv_path)
|
|
726
804
|
if pub_size and priv_size:
|
|
@@ -749,13 +827,29 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
|
|
|
749
827
|
)
|
|
750
828
|
time.sleep(1)
|
|
751
829
|
|
|
752
|
-
|
|
830
|
+
final_pub = file_size(pub_path)
|
|
831
|
+
final_priv = file_size(priv_path)
|
|
832
|
+
if final_pub and final_priv:
|
|
833
|
+
key_res = _run_pct(vmid, f"su - {user} -c 'cat {pub_path}'")
|
|
834
|
+
if not process_exited:
|
|
835
|
+
proc.terminate()
|
|
836
|
+
try:
|
|
837
|
+
proc.wait(timeout=3)
|
|
838
|
+
except subprocess.TimeoutExpired:
|
|
839
|
+
proc.kill()
|
|
840
|
+
return {
|
|
841
|
+
"ok": True,
|
|
842
|
+
"public_key": key_res["stdout"].strip(),
|
|
843
|
+
"history": history,
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if not process_exited:
|
|
753
847
|
proc.terminate()
|
|
754
848
|
try:
|
|
755
849
|
proc.wait(timeout=3)
|
|
756
850
|
except subprocess.TimeoutExpired:
|
|
757
851
|
proc.kill()
|
|
758
|
-
|
|
852
|
+
exit_out, exit_err = proc.communicate(timeout=1)
|
|
759
853
|
history.append(
|
|
760
854
|
{
|
|
761
855
|
"timestamp_s": round(time.time() - start, 2),
|
|
@@ -765,8 +859,8 @@ def _portacode_connect_and_read_key(vmid: int, user: str, timeout_s: int = 10) -
|
|
|
765
859
|
return {
|
|
766
860
|
"ok": False,
|
|
767
861
|
"error": "timed out waiting for portacode key files",
|
|
768
|
-
"stdout": (
|
|
769
|
-
"stderr": (
|
|
862
|
+
"stdout": (exit_out or "").strip(),
|
|
863
|
+
"stderr": (exit_err or "").strip(),
|
|
770
864
|
"history": history,
|
|
771
865
|
}
|
|
772
866
|
|
|
@@ -867,6 +961,7 @@ def _bootstrap_portacode(
|
|
|
867
961
|
progress_callback: Optional[ProgressCallback] = None,
|
|
868
962
|
start_index: int = 1,
|
|
869
963
|
total_steps: Optional[int] = None,
|
|
964
|
+
default_public_key: Optional[str] = None,
|
|
870
965
|
) -> Tuple[str, List[Dict[str, Any]]]:
|
|
871
966
|
actual_steps = steps if steps is not None else _build_bootstrap_steps(user, password, ssh_key)
|
|
872
967
|
results, ok = _run_setup_steps(
|
|
@@ -884,21 +979,38 @@ def _bootstrap_portacode(
|
|
|
884
979
|
history_snippet = ""
|
|
885
980
|
if isinstance(history, list) and history:
|
|
886
981
|
history_snippet = f" history={history[-3:]}"
|
|
982
|
+
command = details.get("cmd")
|
|
983
|
+
command_text = ""
|
|
984
|
+
if command:
|
|
985
|
+
if isinstance(command, (list, tuple)):
|
|
986
|
+
command_text = shlex.join(str(entry) for entry in command)
|
|
987
|
+
else:
|
|
988
|
+
command_text = str(command)
|
|
989
|
+
command_suffix = f" command={command_text}" if command_text else ""
|
|
887
990
|
if summary:
|
|
888
991
|
logger.warning(
|
|
889
|
-
"Portacode bootstrap failure summary=%s%s",
|
|
992
|
+
"Portacode bootstrap failure summary=%s%s%s",
|
|
890
993
|
summary,
|
|
891
994
|
f" history_len={len(history)}" if history else "",
|
|
995
|
+
f" command={command_text}" if command_text else "",
|
|
996
|
+
)
|
|
997
|
+
raise RuntimeError(
|
|
998
|
+
f"Portacode bootstrap steps failed: {summary}{history_snippet}{command_suffix}"
|
|
892
999
|
)
|
|
893
|
-
raise RuntimeError(f"Portacode bootstrap steps failed: {summary}{history_snippet}")
|
|
894
1000
|
raise RuntimeError("Portacode bootstrap steps failed.")
|
|
895
1001
|
key_step = next((entry for entry in results if entry.get("name") == "portacode_connect"), None)
|
|
896
|
-
public_key = key_step.get("public_key") if key_step else
|
|
1002
|
+
public_key = key_step.get("public_key") if key_step else default_public_key
|
|
897
1003
|
if not public_key:
|
|
898
1004
|
raise RuntimeError("Portacode connect did not return a public key.")
|
|
899
1005
|
return public_key, results
|
|
900
1006
|
|
|
901
1007
|
|
|
1008
|
+
def _build_cloudflare_snapshot(cloudflare_config: Dict[str, Any] | None) -> Dict[str, Any]:
|
|
1009
|
+
if not cloudflare_config:
|
|
1010
|
+
return {"configured": False}
|
|
1011
|
+
return {"configured": bool(cloudflare_config.get("api_token"))}
|
|
1012
|
+
|
|
1013
|
+
|
|
902
1014
|
def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
903
1015
|
network = config.get("network", {})
|
|
904
1016
|
base_network = {
|
|
@@ -906,8 +1018,13 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
906
1018
|
"message": network.get("message"),
|
|
907
1019
|
"bridge": network.get("bridge", DEFAULT_BRIDGE),
|
|
908
1020
|
}
|
|
1021
|
+
cloudflare_snapshot = _build_cloudflare_snapshot(config.get("cloudflare"))
|
|
909
1022
|
if not config:
|
|
910
|
-
return {
|
|
1023
|
+
return {
|
|
1024
|
+
"configured": False,
|
|
1025
|
+
"network": base_network,
|
|
1026
|
+
"cloudflare": cloudflare_snapshot,
|
|
1027
|
+
}
|
|
911
1028
|
return {
|
|
912
1029
|
"configured": True,
|
|
913
1030
|
"host": config.get("host"),
|
|
@@ -918,18 +1035,54 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
918
1035
|
"templates": config.get("templates") or [],
|
|
919
1036
|
"last_verified": config.get("last_verified"),
|
|
920
1037
|
"network": base_network,
|
|
1038
|
+
"cloudflare": cloudflare_snapshot,
|
|
921
1039
|
}
|
|
922
1040
|
|
|
923
1041
|
|
|
924
|
-
def
|
|
1042
|
+
def _resolve_proxmox_credentials(
|
|
1043
|
+
token_identifier: Optional[str],
|
|
1044
|
+
token_value: Optional[str],
|
|
1045
|
+
existing: Dict[str, Any],
|
|
1046
|
+
) -> Tuple[str, str, str]:
|
|
1047
|
+
if token_identifier:
|
|
1048
|
+
if not token_value:
|
|
1049
|
+
raise ValueError("token_value is required when providing a new token_identifier")
|
|
1050
|
+
user, token_name = _parse_token(token_identifier)
|
|
1051
|
+
return user, token_name, token_value
|
|
1052
|
+
if existing and existing.get("user") and existing.get("token_name") and existing.get("token_value"):
|
|
1053
|
+
return existing["user"], existing["token_name"], existing["token_value"]
|
|
1054
|
+
raise ValueError("Proxmox token identifier and value are required when no existing configuration is available")
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def _build_cloudflare_config(existing: Dict[str, Any], api_token: Optional[str]) -> Dict[str, Any]:
|
|
1058
|
+
cloudflare: Dict[str, Any] = dict(existing.get("cloudflare", {}) or {})
|
|
1059
|
+
if api_token:
|
|
1060
|
+
cloudflare["api_token"] = api_token
|
|
1061
|
+
if cloudflare.get("api_token"):
|
|
1062
|
+
cloudflare["configured"] = True
|
|
1063
|
+
elif "configured" in cloudflare:
|
|
1064
|
+
cloudflare.pop("configured", None)
|
|
1065
|
+
return cloudflare
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def configure_infrastructure(
|
|
1069
|
+
token_identifier: Optional[str] = None,
|
|
1070
|
+
token_value: Optional[str] = None,
|
|
1071
|
+
verify_ssl: Optional[bool] = None,
|
|
1072
|
+
cloudflare_api_token: Optional[str] = None,
|
|
1073
|
+
) -> Dict[str, Any]:
|
|
925
1074
|
ProxmoxAPI = _ensure_proxmoxer()
|
|
926
|
-
|
|
1075
|
+
existing = _load_config()
|
|
1076
|
+
user, token_name, resolved_token_value = _resolve_proxmox_credentials(
|
|
1077
|
+
token_identifier, token_value, existing
|
|
1078
|
+
)
|
|
1079
|
+
actual_verify_ssl = verify_ssl if verify_ssl is not None else existing.get("verify_ssl", False)
|
|
927
1080
|
client = ProxmoxAPI(
|
|
928
1081
|
DEFAULT_HOST,
|
|
929
1082
|
user=user,
|
|
930
1083
|
token_name=token_name,
|
|
931
|
-
token_value=
|
|
932
|
-
verify_ssl=
|
|
1084
|
+
token_value=resolved_token_value,
|
|
1085
|
+
verify_ssl=actual_verify_ssl,
|
|
933
1086
|
timeout=30,
|
|
934
1087
|
)
|
|
935
1088
|
node = _pick_node(client)
|
|
@@ -937,35 +1090,35 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
|
|
|
937
1090
|
storages = client.nodes(node).storage.get()
|
|
938
1091
|
default_storage = _pick_storage(storages)
|
|
939
1092
|
templates = _list_templates(client, node, storages)
|
|
940
|
-
network
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1093
|
+
network = dict(existing.get("network", {}) or {})
|
|
1094
|
+
if not network.get("applied"):
|
|
1095
|
+
try:
|
|
1096
|
+
network = _ensure_bridge()
|
|
1097
|
+
# Wait for network convergence before validating connectivity
|
|
1098
|
+
time.sleep(2)
|
|
1099
|
+
if not _verify_connectivity():
|
|
1100
|
+
raise RuntimeError("Connectivity check failed; bridge reverted")
|
|
946
1101
|
network["health"] = "healthy"
|
|
947
|
-
|
|
948
|
-
|
|
1102
|
+
except Exception as exc:
|
|
1103
|
+
logger.warning("Bridge setup failed; reverting previous changes: %s", exc)
|
|
949
1104
|
_revert_bridge()
|
|
950
|
-
|
|
951
|
-
network = {"applied": False, "message": str(exc), "bridge": DEFAULT_BRIDGE}
|
|
952
|
-
logger.warning("Bridge setup skipped: %s", exc)
|
|
953
|
-
except Exception as exc: # pragma: no cover - best effort
|
|
954
|
-
network = {"applied": False, "message": str(exc), "bridge": DEFAULT_BRIDGE}
|
|
955
|
-
logger.warning("Bridge setup failed: %s", exc)
|
|
1105
|
+
raise
|
|
956
1106
|
config = {
|
|
957
1107
|
"host": DEFAULT_HOST,
|
|
958
1108
|
"node": node,
|
|
959
1109
|
"user": user,
|
|
960
1110
|
"token_name": token_name,
|
|
961
|
-
"token_value":
|
|
962
|
-
"verify_ssl":
|
|
1111
|
+
"token_value": resolved_token_value,
|
|
1112
|
+
"verify_ssl": actual_verify_ssl,
|
|
963
1113
|
"default_storage": default_storage,
|
|
964
1114
|
"templates": templates,
|
|
965
1115
|
"last_verified": datetime.utcnow().isoformat() + "Z",
|
|
966
1116
|
"network": network,
|
|
967
1117
|
"node_status": status,
|
|
968
1118
|
}
|
|
1119
|
+
cloudflare = _build_cloudflare_config(existing, cloudflare_api_token)
|
|
1120
|
+
if cloudflare:
|
|
1121
|
+
config["cloudflare"] = cloudflare
|
|
969
1122
|
_save_config(config)
|
|
970
1123
|
snapshot = build_snapshot(config)
|
|
971
1124
|
snapshot["node_status"] = status
|
|
@@ -1039,8 +1192,17 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
1039
1192
|
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
1040
1193
|
logger.info("create_proxmox_container command received")
|
|
1041
1194
|
request_id = message.get("request_id")
|
|
1195
|
+
device_id = message.get("device_id")
|
|
1196
|
+
device_public_key = (message.get("device_public_key") or "").strip()
|
|
1197
|
+
device_private_key = (message.get("device_private_key") or "").strip()
|
|
1198
|
+
has_device_keypair = bool(device_public_key and device_private_key)
|
|
1042
1199
|
bootstrap_user, bootstrap_password, bootstrap_ssh_key = _get_provisioning_user_info(message)
|
|
1043
|
-
bootstrap_steps = _build_bootstrap_steps(
|
|
1200
|
+
bootstrap_steps = _build_bootstrap_steps(
|
|
1201
|
+
bootstrap_user,
|
|
1202
|
+
bootstrap_password,
|
|
1203
|
+
bootstrap_ssh_key,
|
|
1204
|
+
include_portacode_connect=not has_device_keypair,
|
|
1205
|
+
)
|
|
1044
1206
|
total_steps = 3 + len(bootstrap_steps) + 2
|
|
1045
1207
|
current_step_index = 1
|
|
1046
1208
|
|
|
@@ -1201,9 +1363,102 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
1201
1363
|
progress_callback=_bootstrap_progress_callback,
|
|
1202
1364
|
start_index=current_step_index,
|
|
1203
1365
|
total_steps=total_steps,
|
|
1366
|
+
default_public_key=device_public_key if has_device_keypair else None,
|
|
1204
1367
|
)
|
|
1205
1368
|
current_step_index += len(bootstrap_steps)
|
|
1206
1369
|
|
|
1370
|
+
service_installed = False
|
|
1371
|
+
if has_device_keypair:
|
|
1372
|
+
logger.info(
|
|
1373
|
+
"deploying dashboard-provided Portacode keypair (device_id=%s) into container %s",
|
|
1374
|
+
device_id,
|
|
1375
|
+
vmid,
|
|
1376
|
+
)
|
|
1377
|
+
_deploy_device_keypair(
|
|
1378
|
+
vmid,
|
|
1379
|
+
payload["username"],
|
|
1380
|
+
device_private_key,
|
|
1381
|
+
device_public_key,
|
|
1382
|
+
)
|
|
1383
|
+
service_installed = True
|
|
1384
|
+
service_start_index = current_step_index
|
|
1385
|
+
|
|
1386
|
+
auth_step_name = "setup_device_authentication"
|
|
1387
|
+
auth_label = "Setting up device authentication"
|
|
1388
|
+
_emit_progress_event(
|
|
1389
|
+
self,
|
|
1390
|
+
step_index=service_start_index,
|
|
1391
|
+
total_steps=total_steps,
|
|
1392
|
+
step_name=auth_step_name,
|
|
1393
|
+
step_label=auth_label,
|
|
1394
|
+
status="in_progress",
|
|
1395
|
+
message="Notifying the server of the new device…",
|
|
1396
|
+
phase="service",
|
|
1397
|
+
request_id=request_id,
|
|
1398
|
+
)
|
|
1399
|
+
_emit_progress_event(
|
|
1400
|
+
self,
|
|
1401
|
+
step_index=service_start_index,
|
|
1402
|
+
total_steps=total_steps,
|
|
1403
|
+
step_name=auth_step_name,
|
|
1404
|
+
step_label=auth_label,
|
|
1405
|
+
status="completed",
|
|
1406
|
+
message="Authentication metadata recorded.",
|
|
1407
|
+
phase="service",
|
|
1408
|
+
request_id=request_id,
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
install_step = service_start_index + 1
|
|
1412
|
+
install_label = "Launching Portacode service"
|
|
1413
|
+
_emit_progress_event(
|
|
1414
|
+
self,
|
|
1415
|
+
step_index=install_step,
|
|
1416
|
+
total_steps=total_steps,
|
|
1417
|
+
step_name="launch_portacode_service",
|
|
1418
|
+
step_label=install_label,
|
|
1419
|
+
status="in_progress",
|
|
1420
|
+
message="Running sudo portacode service install…",
|
|
1421
|
+
phase="service",
|
|
1422
|
+
request_id=request_id,
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
cmd = f"su - {payload['username']} -c 'sudo -S portacode service install'"
|
|
1426
|
+
res = _run_pct(vmid, cmd, input_text=payload["password"] + "\n")
|
|
1427
|
+
|
|
1428
|
+
if res["returncode"] != 0:
|
|
1429
|
+
_emit_progress_event(
|
|
1430
|
+
self,
|
|
1431
|
+
step_index=install_step,
|
|
1432
|
+
total_steps=total_steps,
|
|
1433
|
+
step_name="launch_portacode_service",
|
|
1434
|
+
step_label=install_label,
|
|
1435
|
+
status="failed",
|
|
1436
|
+
message=f"{install_label} failed: {res.get('stderr') or res.get('stdout')}",
|
|
1437
|
+
phase="service",
|
|
1438
|
+
request_id=request_id,
|
|
1439
|
+
details={
|
|
1440
|
+
"stderr": res.get("stderr"),
|
|
1441
|
+
"stdout": res.get("stdout"),
|
|
1442
|
+
},
|
|
1443
|
+
)
|
|
1444
|
+
raise RuntimeError(res.get("stderr") or res.get("stdout") or "Service install failed")
|
|
1445
|
+
|
|
1446
|
+
_emit_progress_event(
|
|
1447
|
+
self,
|
|
1448
|
+
step_index=install_step,
|
|
1449
|
+
total_steps=total_steps,
|
|
1450
|
+
step_name="launch_portacode_service",
|
|
1451
|
+
step_label=install_label,
|
|
1452
|
+
status="completed",
|
|
1453
|
+
message="Portacode service install finished.",
|
|
1454
|
+
phase="service",
|
|
1455
|
+
request_id=request_id,
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
logger.info("create_proxmox_container: portacode service install completed inside ct %s", vmid)
|
|
1459
|
+
|
|
1460
|
+
current_step_index += 2
|
|
1461
|
+
|
|
1207
1462
|
return {
|
|
1208
1463
|
"event": "proxmox_container_created",
|
|
1209
1464
|
"success": True,
|
|
@@ -1220,6 +1475,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
1220
1475
|
"cpus": payload["cpus"],
|
|
1221
1476
|
},
|
|
1222
1477
|
"setup_steps": steps,
|
|
1478
|
+
"device_id": device_id,
|
|
1479
|
+
"service_installed": service_installed,
|
|
1223
1480
|
}
|
|
1224
1481
|
|
|
1225
1482
|
|
|
@@ -1437,10 +1694,13 @@ class ConfigureProxmoxInfraHandler(SyncHandler):
|
|
|
1437
1694
|
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
1438
1695
|
token_identifier = message.get("token_identifier")
|
|
1439
1696
|
token_value = message.get("token_value")
|
|
1440
|
-
verify_ssl =
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1697
|
+
verify_ssl = message.get("verify_ssl")
|
|
1698
|
+
snapshot = configure_infrastructure(
|
|
1699
|
+
token_identifier=token_identifier,
|
|
1700
|
+
token_value=token_value,
|
|
1701
|
+
verify_ssl=verify_ssl,
|
|
1702
|
+
cloudflare_api_token=message.get("cloudflare_api_token"),
|
|
1703
|
+
)
|
|
1444
1704
|
return {
|
|
1445
1705
|
"event": "proxmox_infra_configured",
|
|
1446
1706
|
"success": True,
|