portacode 1.4.12.dev9__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.dev9 → portacode-1.4.15.dev0}/PKG-INFO +1 -1
  2. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/_version.py +2 -2
  3. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +12 -3
  4. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/proxmox_infra.py +284 -37
  5. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/PKG-INFO +1 -1
  8. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/SOURCES.txt +1 -0
  9. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/.claude/agents/communication-manager.md +0 -0
  10. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/.claude/settings.local.json +0 -0
  11. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/.gitignore +0 -0
  12. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/.gitmodules +0 -0
  13. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/LICENSE +0 -0
  14. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/MANIFEST.in +0 -0
  15. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/Makefile +0 -0
  16. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/README.md +0 -0
  17. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/backup.sh +0 -0
  18. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/connect.py +0 -0
  19. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/connect.sh +0 -0
  20. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docker-compose.yaml +0 -0
  21. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docs/images/device-transfer-button.png +0 -0
  22. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docs/images/device-transfer-modal.png +0 -0
  23. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docs/images/pair-device-button.png +0 -0
  24. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docs/images/pairing-request.png +0 -0
  25. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/docs/images/student-workspace.png +0 -0
  26. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/README.md +0 -0
  27. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/simple_device/Dockerfile +0 -0
  28. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/simple_device/README.md +0 -0
  29. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  30. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/simple_device/docker-compose.yaml +0 -0
  32. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/Dockerfile +0 -0
  33. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/README.md +0 -0
  34. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  35. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  37. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  38. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  39. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  40. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  41. {portacode-1.4.12.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  46. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  47. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  48. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  49. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  50. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  51. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  52. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  53. {portacode-1.4.12.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  61. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  62. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  63. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  64. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  66. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  67. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  68. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  69. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  70. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  72. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  73. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  74. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  75. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  76. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  77. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  78. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  79. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  80. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  81. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  82. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  83. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  84. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  85. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  86. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  87. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  88. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  89. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  90. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  92. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  93. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  94. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  95. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  96. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  97. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  98. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  99. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  100. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  101. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  102. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  103. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  104. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  105. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  106. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  107. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  108. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  109. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  110. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  112. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  113. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  114. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  115. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  116. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  117. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  118. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  119. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  120. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  121. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  122. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  123. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  124. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  125. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  126. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  127. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  128. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  129. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  130. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  132. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  133. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  134. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  135. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  136. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  137. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  138. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  139. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  140. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  141. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  142. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  143. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  144. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  145. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  146. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  147. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  148. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  149. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  150. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  152. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  153. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  154. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  155. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  156. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  157. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  158. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  159. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  160. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  161. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  162. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  163. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  164. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  165. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  166. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  167. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  168. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  169. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  170. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  172. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  173. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  174. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  175. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  176. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  177. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  178. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  179. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  180. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  181. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  182. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  183. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  184. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  185. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  186. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  187. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  188. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  189. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  190. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  192. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  193. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  194. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  195. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  196. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  197. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  198. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  199. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  200. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  201. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  202. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  203. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  204. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  205. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  206. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  207. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  208. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  209. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  210. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  212. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  213. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  214. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  215. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  216. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  217. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  218. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  219. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  220. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  221. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  222. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  223. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  224. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  225. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  226. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  227. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  228. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  229. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  230. {portacode-1.4.12.dev9 → 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.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  232. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  233. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  234. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  235. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  236. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  237. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  238. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  239. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  240. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  241. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  242. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  243. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  244. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  245. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  246. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  247. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  248. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  249. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
  250. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
  251. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  252. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  253. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  254. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  255. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  256. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
  257. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  258. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  259. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  260. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  261. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  262. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  263. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  264. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  265. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  266. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  267. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  268. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  269. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/README.md +0 -0
  270. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/__init__.py +0 -0
  271. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/__main__.py +0 -0
  272. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/cli.py +0 -0
  273. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/README.md +0 -0
  274. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/__init__.py +0 -0
  275. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/client.py +0 -0
  276. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/README.md +0 -0
  277. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/__init__.py +0 -0
  278. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/base.py +0 -0
  279. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
  280. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
  281. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  282. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  283. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
  284. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
  285. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  286. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  287. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
  288. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
  289. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
  290. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
  291. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
  292. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/registry.py +0 -0
  293. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/session.py +0 -0
  294. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
  295. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  296. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/handlers/update_handler.py +0 -0
  297. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/multiplex.py +0 -0
  298. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/connection/terminal.py +0 -0
  299. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/data.py +0 -0
  300. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/keypair.py +0 -0
  301. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/__init__.py +0 -0
  302. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  303. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  304. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/elinks +0 -0
  305. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gio-open +0 -0
  306. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gnome-open +0 -0
  307. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
  308. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kde-open +0 -0
  309. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/kfmclient +0 -0
  310. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  311. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  312. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links +0 -0
  313. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/links2 +0 -0
  314. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/lynx +0 -0
  315. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/mate-open +0 -0
  316. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/netsurf +0 -0
  317. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
  318. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/w3m +0 -0
  319. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
  320. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/link_capture/bin/xdg-open +0 -0
  321. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/logging_categories.py +0 -0
  322. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/pairing.py +0 -0
  323. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/service.py +0 -0
  324. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
  325. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
  326. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  327. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/utils/__init__.py +0 -0
  328. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/utils/diff_apply.py +0 -0
  329. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/utils/diff_renderer.py +0 -0
  330. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode/utils/ntp_clock.py +0 -0
  331. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  332. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/entry_points.txt +0 -0
  333. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/requires.txt +0 -0
  334. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/portacode.egg-info/top_level.txt +0 -0
  335. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/pyproject.toml +0 -0
  336. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/restore.sh +0 -0
  337. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/run_tests.py +0 -0
  338. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/setup.cfg +0 -0
  339. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/setup.py +0 -0
  340. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test.sh +0 -0
  341. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/README.md +0 -0
  342. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/__init__.py +0 -0
  343. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_device_online.py +0 -0
  344. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_file_operations.py +0 -0
  345. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_git_status_ui.py +0 -0
  346. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_login_flow.py +0 -0
  347. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
  348. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_play_store_screenshots.py +0 -0
  349. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
  350. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_terminal_interaction.py +0 -0
  351. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
  352. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_modules/test_terminal_start.py +0 -0
  353. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/test_request_id.py +0 -0
  354. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/.env.example +0 -0
  355. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/README.md +0 -0
  356. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/__init__.py +0 -0
  357. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/cli.py +0 -0
  358. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/__init__.py +0 -0
  359. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/base_test.py +0 -0
  360. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/cli_manager.py +0 -0
  361. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
  362. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/playwright_manager.py +0 -0
  363. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/runner.py +0 -0
  364. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
  365. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/core/test_discovery.py +0 -0
  366. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/testing_framework/requirements.txt +0 -0
  367. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  368. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/agent_context_management.md +0 -0
  369. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/django_server_time_sync.md +0 -0
  370. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/device_performance_degradation.md +0 -0
  371. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  372. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
  373. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
  374. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/premature_terminal_exit.md +0 -0
  375. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
  376. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
  377. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  378. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
  379. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/tools/generate_play_store_assets.py +0 -0
  380. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/tools/pairing_tester.py +0 -0
  381. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/tools/run_screenshot_suite.sh +0 -0
  382. {portacode-1.4.12.dev9 → portacode-1.4.15.dev0}/tools/test_python_ntp_clock.py +0 -0
  383. {portacode-1.4.12.dev9 → 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.dev9
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.dev9'
32
- __version_tuple__ = version_tuple = (1, 4, 12, 'dev9')
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)
@@ -880,6 +961,7 @@ def _bootstrap_portacode(
880
961
  progress_callback: Optional[ProgressCallback] = None,
881
962
  start_index: int = 1,
882
963
  total_steps: Optional[int] = None,
964
+ default_public_key: Optional[str] = None,
883
965
  ) -> Tuple[str, List[Dict[str, Any]]]:
884
966
  actual_steps = steps if steps is not None else _build_bootstrap_steps(user, password, ssh_key)
885
967
  results, ok = _run_setup_steps(
@@ -897,21 +979,38 @@ def _bootstrap_portacode(
897
979
  history_snippet = ""
898
980
  if isinstance(history, list) and history:
899
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 ""
900
990
  if summary:
901
991
  logger.warning(
902
- "Portacode bootstrap failure summary=%s%s",
992
+ "Portacode bootstrap failure summary=%s%s%s",
903
993
  summary,
904
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}"
905
999
  )
906
- raise RuntimeError(f"Portacode bootstrap steps failed: {summary}{history_snippet}")
907
1000
  raise RuntimeError("Portacode bootstrap steps failed.")
908
1001
  key_step = next((entry for entry in results if entry.get("name") == "portacode_connect"), None)
909
- 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
910
1003
  if not public_key:
911
1004
  raise RuntimeError("Portacode connect did not return a public key.")
912
1005
  return public_key, results
913
1006
 
914
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
+
915
1014
  def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
916
1015
  network = config.get("network", {})
917
1016
  base_network = {
@@ -919,8 +1018,13 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
919
1018
  "message": network.get("message"),
920
1019
  "bridge": network.get("bridge", DEFAULT_BRIDGE),
921
1020
  }
1021
+ cloudflare_snapshot = _build_cloudflare_snapshot(config.get("cloudflare"))
922
1022
  if not config:
923
- return {"configured": False, "network": base_network}
1023
+ return {
1024
+ "configured": False,
1025
+ "network": base_network,
1026
+ "cloudflare": cloudflare_snapshot,
1027
+ }
924
1028
  return {
925
1029
  "configured": True,
926
1030
  "host": config.get("host"),
@@ -931,18 +1035,54 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
931
1035
  "templates": config.get("templates") or [],
932
1036
  "last_verified": config.get("last_verified"),
933
1037
  "network": base_network,
1038
+ "cloudflare": cloudflare_snapshot,
934
1039
  }
935
1040
 
936
1041
 
937
- 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]:
938
1074
  ProxmoxAPI = _ensure_proxmoxer()
939
- 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)
940
1080
  client = ProxmoxAPI(
941
1081
  DEFAULT_HOST,
942
1082
  user=user,
943
1083
  token_name=token_name,
944
- token_value=token_value,
945
- verify_ssl=verify_ssl,
1084
+ token_value=resolved_token_value,
1085
+ verify_ssl=actual_verify_ssl,
946
1086
  timeout=30,
947
1087
  )
948
1088
  node = _pick_node(client)
@@ -950,35 +1090,35 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
950
1090
  storages = client.nodes(node).storage.get()
951
1091
  default_storage = _pick_storage(storages)
952
1092
  templates = _list_templates(client, node, storages)
953
- network: Dict[str, Any] = {}
954
- try:
955
- network = _ensure_bridge()
956
- # Wait for network convergence before validating connectivity
957
- time.sleep(2)
958
- 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")
959
1101
  network["health"] = "healthy"
960
- else:
961
- 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)
962
1104
  _revert_bridge()
963
- except PermissionError as exc:
964
- network = {"applied": False, "message": str(exc), "bridge": DEFAULT_BRIDGE}
965
- logger.warning("Bridge setup skipped: %s", exc)
966
- except Exception as exc: # pragma: no cover - best effort
967
- network = {"applied": False, "message": str(exc), "bridge": DEFAULT_BRIDGE}
968
- logger.warning("Bridge setup failed: %s", exc)
1105
+ raise
969
1106
  config = {
970
1107
  "host": DEFAULT_HOST,
971
1108
  "node": node,
972
1109
  "user": user,
973
1110
  "token_name": token_name,
974
- "token_value": token_value,
975
- "verify_ssl": verify_ssl,
1111
+ "token_value": resolved_token_value,
1112
+ "verify_ssl": actual_verify_ssl,
976
1113
  "default_storage": default_storage,
977
1114
  "templates": templates,
978
1115
  "last_verified": datetime.utcnow().isoformat() + "Z",
979
1116
  "network": network,
980
1117
  "node_status": status,
981
1118
  }
1119
+ cloudflare = _build_cloudflare_config(existing, cloudflare_api_token)
1120
+ if cloudflare:
1121
+ config["cloudflare"] = cloudflare
982
1122
  _save_config(config)
983
1123
  snapshot = build_snapshot(config)
984
1124
  snapshot["node_status"] = status
@@ -1052,8 +1192,17 @@ class CreateProxmoxContainerHandler(SyncHandler):
1052
1192
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1053
1193
  logger.info("create_proxmox_container command received")
1054
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)
1055
1199
  bootstrap_user, bootstrap_password, bootstrap_ssh_key = _get_provisioning_user_info(message)
1056
- 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
+ )
1057
1206
  total_steps = 3 + len(bootstrap_steps) + 2
1058
1207
  current_step_index = 1
1059
1208
 
@@ -1214,9 +1363,102 @@ class CreateProxmoxContainerHandler(SyncHandler):
1214
1363
  progress_callback=_bootstrap_progress_callback,
1215
1364
  start_index=current_step_index,
1216
1365
  total_steps=total_steps,
1366
+ default_public_key=device_public_key if has_device_keypair else None,
1217
1367
  )
1218
1368
  current_step_index += len(bootstrap_steps)
1219
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
+
1220
1462
  return {
1221
1463
  "event": "proxmox_container_created",
1222
1464
  "success": True,
@@ -1233,6 +1475,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
1233
1475
  "cpus": payload["cpus"],
1234
1476
  },
1235
1477
  "setup_steps": steps,
1478
+ "device_id": device_id,
1479
+ "service_installed": service_installed,
1236
1480
  }
1237
1481
 
1238
1482
 
@@ -1450,10 +1694,13 @@ class ConfigureProxmoxInfraHandler(SyncHandler):
1450
1694
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
1451
1695
  token_identifier = message.get("token_identifier")
1452
1696
  token_value = message.get("token_value")
1453
- verify_ssl = bool(message.get("verify_ssl"))
1454
- if not token_identifier or not token_value:
1455
- raise ValueError("token_identifier and token_value are required")
1456
- 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
+ )
1457
1704
  return {
1458
1705
  "event": "proxmox_infra_configured",
1459
1706
  "success": True,