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.
Files changed (383) hide show
  1. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/PKG-INFO +1 -1
  2. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/_version.py +2 -2
  3. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +12 -3
  4. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/proxmox_infra.py +309 -49
  5. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/system_handlers.py +131 -2
  6. portacode-1.4.15.dev0/portacode/connection/handlers/test_proxmox_infra.py +13 -0
  7. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/PKG-INFO +1 -1
  8. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/SOURCES.txt +1 -0
  9. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.claude/agents/communication-manager.md +0 -0
  10. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.claude/settings.local.json +0 -0
  11. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.gitignore +0 -0
  12. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/.gitmodules +0 -0
  13. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/LICENSE +0 -0
  14. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/MANIFEST.in +0 -0
  15. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/Makefile +0 -0
  16. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/README.md +0 -0
  17. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/backup.sh +0 -0
  18. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/connect.py +0 -0
  19. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/connect.sh +0 -0
  20. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docker-compose.yaml +0 -0
  21. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/device-transfer-button.png +0 -0
  22. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/device-transfer-modal.png +0 -0
  23. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/pair-device-button.png +0 -0
  24. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/pairing-request.png +0 -0
  25. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/docs/images/student-workspace.png +0 -0
  26. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/README.md +0 -0
  27. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/Dockerfile +0 -0
  28. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/README.md +0 -0
  29. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  30. {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
  31. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/simple_device/docker-compose.yaml +0 -0
  32. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/Dockerfile +0 -0
  33. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/README.md +0 -0
  34. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  35. {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
  36. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  37. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  38. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  39. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  40. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  41. {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
  42. {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
  43. {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
  44. {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
  45. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  46. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  47. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  48. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  49. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  50. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  51. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  52. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  61. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  62. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  63. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  64. {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
  65. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  66. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  67. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  68. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  69. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  70. {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
  71. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  72. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  73. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  74. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  75. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  76. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  77. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  78. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  79. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  80. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  81. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  82. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  83. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  84. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  85. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  86. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  87. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  88. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  89. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  90. {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
  91. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  92. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  93. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  94. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  95. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  96. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  97. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  98. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  99. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  100. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  101. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  102. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  103. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  104. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  105. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  106. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  107. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  108. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  109. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  110. {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
  111. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  112. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  113. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  114. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  115. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  116. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  117. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  118. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  119. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  120. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  121. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  122. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  123. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  124. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  125. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  126. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  127. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  128. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  129. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  130. {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
  131. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  132. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  133. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  134. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  135. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  136. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  137. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  138. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  139. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  140. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  141. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  142. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  143. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  144. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  145. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  146. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  147. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  148. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  149. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  150. {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
  151. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  152. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  153. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  154. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  155. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  156. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  157. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  158. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  159. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  160. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  161. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  162. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  163. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  164. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  165. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  166. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  167. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  168. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  169. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  170. {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
  171. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  172. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  173. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  174. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  175. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  176. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  177. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  178. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  179. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  180. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  181. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  182. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  183. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  184. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  185. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  186. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  187. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  188. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  189. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  190. {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
  191. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  192. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  193. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  194. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  195. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  196. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  197. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  198. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  199. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  200. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  201. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  202. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  203. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  204. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  205. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  206. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  207. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  208. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  209. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  210. {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
  211. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  212. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  213. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  214. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  215. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  216. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  217. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  218. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  219. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  220. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  221. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  222. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  223. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  224. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  225. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  226. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  227. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  228. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  229. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  230. {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
  231. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  232. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  233. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  234. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  235. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  236. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  237. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  238. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  239. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  240. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  241. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  242. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  243. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  244. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  245. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  246. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  247. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  248. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  249. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
  250. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
  251. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  252. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  253. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  254. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  255. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  256. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
  257. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  258. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  259. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  260. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  261. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  262. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  263. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  264. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  265. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  266. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  267. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  268. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  269. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/README.md +0 -0
  270. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/__init__.py +0 -0
  271. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/__main__.py +0 -0
  272. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/cli.py +0 -0
  273. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/README.md +0 -0
  274. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/__init__.py +0 -0
  275. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/client.py +0 -0
  276. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/README.md +0 -0
  277. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/__init__.py +0 -0
  278. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/base.py +0 -0
  279. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
  280. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
  281. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  282. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  283. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
  284. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
  285. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  286. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  287. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
  288. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
  289. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
  290. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
  291. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
  292. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/registry.py +0 -0
  293. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/session.py +0 -0
  294. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
  295. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  296. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/handlers/update_handler.py +0 -0
  297. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/multiplex.py +0 -0
  298. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/connection/terminal.py +0 -0
  299. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/data.py +0 -0
  300. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/keypair.py +0 -0
  301. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/__init__.py +0 -0
  302. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  303. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  304. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/elinks +0 -0
  305. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gio-open +0 -0
  306. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gnome-open +0 -0
  307. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
  308. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kde-open +0 -0
  309. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kfmclient +0 -0
  310. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  311. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  312. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links +0 -0
  313. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links2 +0 -0
  314. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/lynx +0 -0
  315. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/mate-open +0 -0
  316. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/netsurf +0 -0
  317. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
  318. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/w3m +0 -0
  319. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
  320. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/link_capture/bin/xdg-open +0 -0
  321. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/logging_categories.py +0 -0
  322. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/pairing.py +0 -0
  323. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/service.py +0 -0
  324. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
  325. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
  326. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  327. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/__init__.py +0 -0
  328. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/diff_apply.py +0 -0
  329. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/diff_renderer.py +0 -0
  330. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode/utils/ntp_clock.py +0 -0
  331. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  332. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/entry_points.txt +0 -0
  333. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/requires.txt +0 -0
  334. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/portacode.egg-info/top_level.txt +0 -0
  335. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/pyproject.toml +0 -0
  336. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/restore.sh +0 -0
  337. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/run_tests.py +0 -0
  338. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/setup.cfg +0 -0
  339. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/setup.py +0 -0
  340. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test.sh +0 -0
  341. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/README.md +0 -0
  342. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/__init__.py +0 -0
  343. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_device_online.py +0 -0
  344. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_file_operations.py +0 -0
  345. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_git_status_ui.py +0 -0
  346. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_login_flow.py +0 -0
  347. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
  348. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_play_store_screenshots.py +0 -0
  349. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
  350. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_interaction.py +0 -0
  351. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
  352. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_modules/test_terminal_start.py +0 -0
  353. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/test_request_id.py +0 -0
  354. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/.env.example +0 -0
  355. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/README.md +0 -0
  356. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/__init__.py +0 -0
  357. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/cli.py +0 -0
  358. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/__init__.py +0 -0
  359. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/base_test.py +0 -0
  360. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/cli_manager.py +0 -0
  361. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
  362. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/playwright_manager.py +0 -0
  363. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/runner.py +0 -0
  364. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
  365. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/core/test_discovery.py +0 -0
  366. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/testing_framework/requirements.txt +0 -0
  367. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  368. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/agent_context_management.md +0 -0
  369. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/django_server_time_sync.md +0 -0
  370. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/device_performance_degradation.md +0 -0
  371. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  372. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
  373. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
  374. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/premature_terminal_exit.md +0 -0
  375. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
  376. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
  377. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  378. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
  379. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/generate_play_store_assets.py +0 -0
  380. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/pairing_tester.py +0 -0
  381. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/run_screenshot_suite.sh +0 -0
  382. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/tools/test_python_ntp_clock.py +0 -0
  383. {portacode-1.4.12.dev8 → portacode-1.4.15.dev0}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.12.dev8
3
+ Version: 1.4.15.dev0
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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.12.dev8'
32
- __version_tuple__ = version_tuple = (1, 4, 12, 'dev8')
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
@@ -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, required): API token identifier in the form `user@realm!tokenid`.
330
- * `token_value` (string, required): Secret value associated with the token.
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.
@@ -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=True)
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(user: str, password: str, ssh_key: str) -> List[Dict[str, Any]]:
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
- {"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
470
- {"name": "install_portacode", "cmd": "python3 -m pip install --upgrade portacode", "retries": 0},
471
- {"name": "portacode_connect", "type": "portacode_connect", "timeout_s": 30},
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
- out, err = proc.communicate(timeout=1)
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
- return {
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
- if stable < 1:
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
- out, err = proc.communicate(timeout=1)
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": (out or "").strip(),
769
- "stderr": (err or "").strip(),
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 None
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 {"configured": False, "network": base_network}
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 configure_infrastructure(token_identifier: str, token_value: str, verify_ssl: bool = False) -> Dict[str, Any]:
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
- user, token_name = _parse_token(token_identifier)
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=token_value,
932
- verify_ssl=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: Dict[str, Any] = {}
941
- try:
942
- network = _ensure_bridge()
943
- # Wait for network convergence before validating connectivity
944
- time.sleep(2)
945
- if _verify_connectivity():
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
- else:
948
- network = {"applied": False, "bridge": DEFAULT_BRIDGE, "message": "Connectivity check failed; bridge reverted"}
1102
+ except Exception as exc:
1103
+ logger.warning("Bridge setup failed; reverting previous changes: %s", exc)
949
1104
  _revert_bridge()
950
- except PermissionError as exc:
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": token_value,
962
- "verify_ssl": 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(bootstrap_user, bootstrap_password, bootstrap_ssh_key)
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 = bool(message.get("verify_ssl"))
1441
- if not token_identifier or not token_value:
1442
- raise ValueError("token_identifier and token_value are required")
1443
- snapshot = configure_infrastructure(token_identifier, token_value, verify_ssl=verify_ssl)
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,