portacode 1.5.31.dev3__tar.gz → 1.5.32.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 (449) hide show
  1. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/PKG-INFO +1 -1
  2. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/_version.py +2 -2
  3. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/cloudflare_forwarding.py +179 -15
  4. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/proxmox_infra.py +148 -36
  5. portacode-1.5.32.dev0/portacode/connection/handlers/test_cloudflare_forwarding.py +101 -0
  6. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/test_proxmox_infra.py +151 -0
  7. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/provisioning_cache.py +1 -1
  8. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/forwarding_state.py +5 -1
  9. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/PKG-INFO +1 -1
  10. portacode-1.5.31.dev3/portacode/connection/handlers/test_cloudflare_forwarding.py +0 -56
  11. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/.claude/agents/communication-manager.md +0 -0
  12. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/.claude/settings.local.json +0 -0
  13. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/.gitignore +0 -0
  14. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/.gitmodules +0 -0
  15. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/AGENTS.md +0 -0
  16. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/LICENSE +0 -0
  17. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/MANIFEST.in +0 -0
  18. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/Makefile +0 -0
  19. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/README.md +0 -0
  20. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/build_android.sh +0 -0
  21. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/connect.py +0 -0
  22. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/connect.sh +0 -0
  23. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/cloudflared-domain-connect-containers-audit.md +0 -0
  24. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/creative-team-brief-portacode.md +0 -0
  25. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/device-health-alerting-plan.md +0 -0
  26. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/devops-messaging-ab-tests.md +0 -0
  27. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/homepage-dashboard-positioning-fixes.md +0 -0
  28. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/docs/template-guide.html +0 -0
  29. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/README.md +0 -0
  30. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/simple_device/Dockerfile +0 -0
  31. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/simple_device/README.md +0 -0
  32. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
  33. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  34. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/simple_device/docker-compose.yaml +0 -0
  35. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/Dockerfile +0 -0
  36. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/README.md +0 -0
  37. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
  38. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
  39. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
  40. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
  41. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
  42. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
  43. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
  44. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
  45. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
  46. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
  47. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
  48. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
  49. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
  50. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
  51. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
  52. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
  53. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
  54. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
  55. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
  56. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
  57. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
  58. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
  59. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
  60. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
  61. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
  62. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
  63. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
  64. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
  65. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
  66. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
  67. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  68. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
  69. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
  70. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
  71. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
  72. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
  73. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
  74. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
  75. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
  76. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
  77. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
  78. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
  79. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
  80. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
  81. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
  82. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
  83. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
  84. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
  85. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
  86. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
  87. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
  88. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
  89. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
  90. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
  91. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
  92. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
  93. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
  94. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
  95. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
  96. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
  97. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
  98. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
  99. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
  100. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
  101. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
  102. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
  103. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
  104. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
  105. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
  106. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
  107. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
  108. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
  109. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
  110. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
  111. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
  112. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
  113. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
  114. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
  115. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
  116. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
  117. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
  118. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
  119. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
  120. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
  121. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
  122. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
  123. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
  124. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
  125. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
  126. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
  127. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
  128. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
  129. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
  130. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
  131. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
  132. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
  133. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
  134. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
  135. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
  136. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
  137. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
  138. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
  139. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
  140. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
  141. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
  142. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
  143. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
  144. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
  145. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
  146. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
  147. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
  148. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
  149. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
  150. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
  151. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
  152. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
  153. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
  154. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
  155. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
  156. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
  157. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
  158. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
  159. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
  160. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
  161. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
  162. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
  163. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
  164. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
  165. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
  166. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
  167. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
  168. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
  169. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
  170. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
  171. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
  172. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
  173. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
  174. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
  175. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
  176. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
  177. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
  178. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
  179. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
  180. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
  181. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
  182. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
  183. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
  184. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
  185. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
  186. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
  187. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
  188. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
  189. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
  190. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
  191. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
  192. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
  193. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
  194. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
  195. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
  196. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
  197. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
  198. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
  199. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
  200. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
  201. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
  202. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
  203. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
  204. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
  205. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
  206. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
  207. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
  208. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
  209. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
  210. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
  211. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
  212. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
  213. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
  214. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
  215. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
  216. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
  217. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
  218. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
  219. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
  220. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
  221. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
  222. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
  223. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
  224. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
  225. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
  226. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
  227. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
  228. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
  229. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
  230. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
  231. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
  232. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
  233. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
  234. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
  235. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
  236. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
  237. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
  238. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
  239. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
  240. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
  241. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
  242. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
  243. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
  244. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
  245. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
  246. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
  247. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
  248. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
  249. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
  250. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
  251. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
  252. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/docker-compose.yaml +0 -0
  253. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/README.md +0 -0
  254. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
  255. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
  256. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
  257. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
  258. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
  259. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/manage.py +0 -0
  260. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
  261. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
  262. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
  263. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
  264. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
  265. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
  266. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
  267. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
  268. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
  269. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
  270. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
  271. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
  272. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/README.md +0 -0
  273. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/__init__.py +0 -0
  274. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/__main__.py +0 -0
  275. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/cli.py +0 -0
  276. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/codex_loopback_proxy.py +0 -0
  277. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/codex_prepare.py +0 -0
  278. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/codex_usage_limit.py +0 -0
  279. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/README.md +0 -0
  280. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/__init__.py +0 -0
  281. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/client.py +0 -0
  282. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/codex_app_server.py +0 -0
  283. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/README.md +0 -0
  284. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  285. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/__init__.py +0 -0
  286. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/automation_v2_handlers.py +0 -0
  287. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/base.py +0 -0
  288. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/browser_handlers.py +0 -0
  289. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/chunked_content.py +0 -0
  290. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/cloudflare_tunnel.py +0 -0
  291. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/codex_handlers.py +0 -0
  292. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/diff_handlers.py +0 -0
  293. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  294. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/file_transfer_handlers.py +0 -0
  295. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
  296. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/README.md +0 -0
  297. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/__init__.py +0 -0
  298. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
  299. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/git_manager.py +0 -0
  300. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/handlers.py +0 -0
  301. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/manager.py +0 -0
  302. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/models.py +0 -0
  303. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/test_handlers.py +0 -0
  304. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state/utils.py +0 -0
  305. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/project_state_handlers.py +0 -0
  306. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/registry.py +0 -0
  307. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/resumable_transfer_handlers.py +0 -0
  308. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/runtime_user.py +0 -0
  309. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/session.py +0 -0
  310. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
  311. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/tab_factory.py +0 -0
  312. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  313. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/test_browser_handlers.py +0 -0
  314. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/test_file_move_copy.py +0 -0
  315. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/test_runtime_user.py +0 -0
  316. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/handlers/update_handler.py +0 -0
  317. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/multiplex.py +0 -0
  318. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/terminal.py +0 -0
  319. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/test_webmin_proxy_config.py +0 -0
  320. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/connection/webmin_proxy_config.py +0 -0
  321. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/data.py +0 -0
  322. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/exit_codes.py +0 -0
  323. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/github_credential.py +0 -0
  324. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/keypair.py +0 -0
  325. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/__init__.py +0 -0
  326. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  327. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/__pycache__/__init__.cpython-38.pyc +0 -0
  328. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  329. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-38.pyc +0 -0
  330. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/elinks +0 -0
  331. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/gio-open +0 -0
  332. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/gnome-open +0 -0
  333. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/gvfs-open +0 -0
  334. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/kde-open +0 -0
  335. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/kfmclient +0 -0
  336. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
  337. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
  338. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/links +0 -0
  339. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/links2 +0 -0
  340. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/lynx +0 -0
  341. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/mate-open +0 -0
  342. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/netsurf +0 -0
  343. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/sensible-browser +0 -0
  344. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/w3m +0 -0
  345. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/x-www-browser +0 -0
  346. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/link_capture/bin/xdg-open +0 -0
  347. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/logging_categories.py +0 -0
  348. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/pairing.py +0 -0
  349. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/restart.py +0 -0
  350. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/service.py +0 -0
  351. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/singleton.py +0 -0
  352. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/static/js/test-ntp-clock.html +0 -0
  353. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/static/js/utils/ntp-clock.js +0 -0
  354. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/test_runtime_paths.py +0 -0
  355. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/test_service.py +0 -0
  356. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/test_singleton.py +0 -0
  357. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/test_terminal_exposed_services.py +0 -0
  358. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_codex_app_server.py +0 -0
  359. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_codex_handlers.py +0 -0
  360. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_codex_loopback_proxy.py +0 -0
  361. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_codex_prepare.py +0 -0
  362. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_github_credential.py +0 -0
  363. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_image_read_handler.py +0 -0
  364. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_resumable_transfer_handlers.py +0 -0
  365. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tests/test_system_handlers.py +0 -0
  366. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/__init__.py +0 -0
  367. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/cloudflared_login.py +0 -0
  368. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/ensure_cloudflared.py +0 -0
  369. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/ensure_pyyaml.py +0 -0
  370. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/get_domain.py +0 -0
  371. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/privileged.py +0 -0
  372. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/service_install.py +0 -0
  373. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/tunneling/state.py +0 -0
  374. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/updater.py +0 -0
  375. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
  376. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/__init__.py +0 -0
  377. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/diff_apply.py +0 -0
  378. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/diff_renderer.py +0 -0
  379. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/ntp_clock.py +0 -0
  380. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode/utils/runtime_paths.py +0 -0
  381. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/SOURCES.txt +0 -0
  382. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  383. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/entry_points.txt +0 -0
  384. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/requires.txt +0 -0
  385. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode.egg-info/top_level.txt +0 -0
  386. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/.gitignore +0 -0
  387. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/README.md +0 -0
  388. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/persistent_workspace/Dockerfile +0 -0
  389. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/persistent_workspace/README.md +0 -0
  390. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/persistent_workspace/entrypoint.sh +0 -0
  391. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/persistent_workspace/manage_classroom.py +0 -0
  392. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/Dockerfile +0 -0
  393. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/README.md +0 -0
  394. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/docker-compose.yaml +0 -0
  395. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/docs/device_transfere.png +0 -0
  396. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/docs/pairing_approval.png +0 -0
  397. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/portacode_for_school/temporary_workspace/entrypoint.sh +0 -0
  398. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/pyproject.toml +0 -0
  399. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/run_tests.py +0 -0
  400. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/scripts/diagnose_codex_gateway.py +0 -0
  401. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/setup.cfg +0 -0
  402. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/setup.py +0 -0
  403. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test.sh +0 -0
  404. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/README.md +0 -0
  405. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/__init__.py +0 -0
  406. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_device_online.py +0 -0
  407. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_file_operations.py +0 -0
  408. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_git_status_ui.py +0 -0
  409. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_login_flow.py +0 -0
  410. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_navigate_testing_folder.py +0 -0
  411. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_play_store_screenshots.py +0 -0
  412. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_terminal_buffer_performance.py +0 -0
  413. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_terminal_interaction.py +0 -0
  414. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_terminal_loading_race_condition.py +0 -0
  415. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_modules/test_terminal_start.py +0 -0
  416. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_request_id.py +0 -0
  417. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/test_session_terminal_shutdown.py +0 -0
  418. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/.env.example +0 -0
  419. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/README.md +0 -0
  420. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/__init__.py +0 -0
  421. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/cli.py +0 -0
  422. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/__init__.py +0 -0
  423. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/base_test.py +0 -0
  424. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/cli_manager.py +0 -0
  425. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/hierarchical_runner.py +0 -0
  426. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/playwright_manager.py +0 -0
  427. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/runner.py +0 -0
  428. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/shared_cli_manager.py +0 -0
  429. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/core/test_discovery.py +0 -0
  430. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/testing_framework/requirements.txt +0 -0
  431. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
  432. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/UI_UX/server_occasionally_stops_communicating_with_all_devices.md +0 -0
  433. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/agent_context_management.md +0 -0
  434. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/django_server_time_sync.md +0 -0
  435. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/device_performance_degradation.md +0 -0
  436. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
  437. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/indefinite_resource_loading.md +0 -0
  438. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/portacode_service_silently_down.md +0 -0
  439. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/premature_terminal_exit.md +0 -0
  440. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/project_cpu_hotspots.md +0 -0
  441. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/terminals_exit_upon_starting.md +0 -0
  442. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/websocket_client_silently_dead.md +0 -0
  443. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
  444. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/todo/smartphone_terminal_input_frustrations.md +0 -0
  445. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/tools/generate_play_store_assets.py +0 -0
  446. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/tools/pairing_tester.py +0 -0
  447. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/tools/run_screenshot_suite.sh +0 -0
  448. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/tools/test_python_ntp_clock.py +0 -0
  449. {portacode-1.5.31.dev3 → portacode-1.5.32.dev0}/validate.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.5.31.dev3
3
+ Version: 1.5.32.dev0
4
4
  Summary: Connect real machines to the Portacode AI development environment
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.5.31.dev3'
22
- __version_tuple__ = version_tuple = (1, 5, 31, 'dev3')
21
+ __version__ = version = '1.5.32.dev0'
22
+ __version_tuple__ = version_tuple = (1, 5, 32, 'dev0')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -123,13 +123,16 @@ def _normalize_rules(
123
123
  validated_hostname = _validate_hostname(hostname or "", domain)
124
124
  if parsed is None:
125
125
  parsed = _parse_destination(destination or "")
126
- normalized.append(
127
- {
126
+ normalized_rule = {
128
127
  "hostname": validated_hostname,
129
128
  "destination": destination or "",
130
129
  "parsed": parsed,
131
130
  }
132
- )
131
+ if from_storage:
132
+ resolved_ip = str(entry.get("resolved_ip") or "").strip()
133
+ if resolved_ip:
134
+ normalized_rule["resolved_ip"] = resolved_ip
135
+ normalized.append(normalized_rule)
133
136
  return normalized
134
137
 
135
138
 
@@ -221,7 +224,17 @@ def _resolve_service_endpoint(
221
224
  ip = cache[device_id]
222
225
  else:
223
226
  vmid = _resolve_device_vmid(device_id, proxmox, node)
224
- ip = _find_container_ip(proxmox, node, vmid, leases)
227
+ try:
228
+ ip = _find_container_ip(proxmox, node, vmid, leases)
229
+ except RuntimeError:
230
+ ip = str(parsed.get("resolved_ip") or "").strip()
231
+ if not ip:
232
+ raise
233
+ logger.info(
234
+ "Using last resolved IP for offline exposed device device_id=%s ip=%s",
235
+ device_id,
236
+ ip,
237
+ )
225
238
  cache[device_id] = ip
226
239
  port = parsed["port"]
227
240
  scheme = parsed["scheme"]
@@ -235,8 +248,12 @@ def _build_ingress_entries(
235
248
  leases = _load_leases() if proxmox else []
236
249
  device_ip_cache: Dict[str, str] = {}
237
250
  for rule in rules:
238
- parsed = rule["parsed"]
251
+ parsed = dict(rule["parsed"])
252
+ if rule.get("resolved_ip"):
253
+ parsed["resolved_ip"] = rule["resolved_ip"]
239
254
  service = _resolve_service_endpoint(parsed, proxmox, node, leases, device_ip_cache)
255
+ if parsed["type"] == "device":
256
+ rule["resolved_ip"] = urlparse(service).hostname
240
257
  entry: Dict[str, Any] = {"hostname": rule["hostname"], "service": service}
241
258
  path = parsed.get("path", "")
242
259
  if path:
@@ -254,6 +271,35 @@ def _build_ingress_entries(
254
271
  return entries
255
272
 
256
273
 
274
+ def _load_active_config_resolved_ips(tunnel_state: Dict[str, Any]) -> Dict[str, str]:
275
+ """Recover last working hostname endpoints when upgrading old forwarding state."""
276
+ cfg = tunnel_state.get("config_path")
277
+ config_path = Path(str(cfg)) if cfg else Path(default_config_path())
278
+ try:
279
+ lines = config_path.read_text(encoding="utf-8").splitlines()
280
+ except OSError:
281
+ return {}
282
+ resolved: Dict[str, str] = {}
283
+ hostname = ""
284
+ for raw_line in lines:
285
+ stripped = raw_line.strip()
286
+ if stripped.startswith("- hostname:"):
287
+ hostname = stripped.split(":", 1)[1].strip()
288
+ continue
289
+ if hostname and stripped.startswith("service:"):
290
+ service = stripped.split(":", 1)[1].strip()
291
+ parsed = urlparse(service)
292
+ if parsed.hostname:
293
+ try:
294
+ ipaddress.ip_address(parsed.hostname)
295
+ except ValueError:
296
+ pass
297
+ else:
298
+ resolved[hostname] = parsed.hostname
299
+ hostname = ""
300
+ return resolved
301
+
302
+
257
303
  def _format_config_value(value: Any) -> str:
258
304
  if isinstance(value, bool):
259
305
  return "true" if value else "false"
@@ -357,10 +403,28 @@ def _load_tunnel_state() -> Dict[str, Any]:
357
403
 
358
404
 
359
405
  def _sanitize_rules(rules: List[Dict[str, Any]]) -> List[Dict[str, str]]:
360
- return [
361
- {"hostname": rule["hostname"], "destination": rule["destination"]}
362
- for rule in rules
363
- ]
406
+ sanitized = []
407
+ for rule in rules:
408
+ item = {"hostname": rule["hostname"], "destination": rule["destination"]}
409
+ if rule.get("resolved_ip"):
410
+ item["resolved_ip"] = rule["resolved_ip"]
411
+ sanitized.append(item)
412
+ return sanitized
413
+
414
+
415
+ def _node_device_id(tunnel_state: Dict[str, Any]) -> str:
416
+ explicit = str(tunnel_state.get("node_device_id") or "").strip()
417
+ if explicit.isdigit():
418
+ return explicit
419
+ match = re.fullmatch(r"portacode-proxmox-(\d+)", str(tunnel_state.get("tunnel_name") or ""))
420
+ if not match:
421
+ raise RuntimeError("Unable to determine node device ID for wildcard forwarding")
422
+ return match.group(1)
423
+
424
+
425
+ def _wildcard_hostname(tunnel_state: Dict[str, Any]) -> str:
426
+ domain = str(tunnel_state.get("domain") or "").strip().lower().rstrip(".")
427
+ return f"*.{_node_device_id(tunnel_state)}.{domain}"
364
428
 
365
429
 
366
430
  def _apply_and_persist_forwarding_rules(
@@ -373,6 +437,11 @@ def _apply_and_persist_forwarding_rules(
373
437
  if not domain or not tunnel_name:
374
438
  raise RuntimeError("Cloudflare domain or tunnel name missing from state.")
375
439
 
440
+ active_resolved_ips = _load_active_config_resolved_ips(tunnel_state)
441
+ for rule in rules:
442
+ if not rule.get("resolved_ip") and rule.get("hostname") in active_resolved_ips:
443
+ rule["resolved_ip"] = active_resolved_ips[rule["hostname"]]
444
+
376
445
  operation_id = f"apply-{time.monotonic_ns()}"
377
446
  total_started = time.monotonic()
378
447
  requires_proxmox = any(rule["parsed"]["type"] == "device" for rule in rules)
@@ -389,7 +458,10 @@ def _apply_and_persist_forwarding_rules(
389
458
  started = time.monotonic()
390
459
  _write_cloudflared_config(tunnel_state, entries)
391
460
  _timed_stage(operation_id, "write_cloudflared_config", started, entry_count=len(entries))
392
- hostnames = [entry["hostname"] for entry in entries if entry.get("hostname")]
461
+ forwarding_state = load_forwarding_state()
462
+ desired_wildcard = _wildcard_hostname(tunnel_state)
463
+ provisioned_wildcard = str(forwarding_state.get("wildcard_dns_hostname") or "")
464
+ hostnames = [] if provisioned_wildcard == desired_wildcard else [desired_wildcard]
393
465
  if hostnames:
394
466
  started = time.monotonic()
395
467
  _route_dns(hostnames, tunnel_name)
@@ -398,7 +470,9 @@ def _apply_and_persist_forwarding_rules(
398
470
  _reload_cloudflared_service()
399
471
  _timed_stage(operation_id, "reload_cloudflared_call", started)
400
472
  started = time.monotonic()
401
- state = persist_forwarding_state(rules)
473
+ state = persist_forwarding_state(
474
+ rules, metadata={"wildcard_dns_hostname": desired_wildcard}
475
+ )
402
476
  _timed_stage(operation_id, "persist_forwarding_state", started, rule_count=len(rules))
403
477
  _timed_stage(operation_id, "apply_total", total_started, rule_count=len(rules))
404
478
  return {
@@ -418,6 +492,20 @@ def _normalize_subdomain_label(subdomain: Any, device_id: str, index: int) -> st
418
492
  return candidate
419
493
 
420
494
 
495
+ def _new_container_hostname(
496
+ device_id: str,
497
+ port: int,
498
+ protocol: str,
499
+ index: int,
500
+ tunnel_state: Dict[str, Any],
501
+ ) -> str:
502
+ node_id = _node_device_id(tunnel_state)
503
+ domain = str(tunnel_state.get("domain") or "").strip().lower().rstrip(".")
504
+ protocol_suffix = "-https" if protocol == "https" else ""
505
+ label = device_id if index == 0 else f"{device_id}-{port}{protocol_suffix}"
506
+ return f"{label}.{node_id}.{domain}"
507
+
508
+
421
509
  def _normalize_container_rule_specs(
422
510
  container_device_id: Any,
423
511
  container_rules: List[Dict[str, Any]],
@@ -743,18 +831,40 @@ def set_container_forwarding_rules(
743
831
  for rule in existing_rules
744
832
  if not _rule_targets_container(rule, normalized_device_id, domain)
745
833
  ]
834
+ existing_target_rules = [
835
+ rule
836
+ for rule in existing_rules
837
+ if _rule_targets_container(rule, normalized_device_id, domain)
838
+ ]
839
+ reusable_hostnames: Dict[tuple[int, str], str] = {}
840
+ recorded_target_ip = ""
841
+ for rule in existing_target_rules:
842
+ parsed = rule.get("parsed") or {}
843
+ if parsed.get("type") != "device":
844
+ continue
845
+ reusable_hostnames[(int(parsed["port"]), str(parsed["scheme"]))] = rule["hostname"]
846
+ recorded_target_ip = str(rule.get("resolved_ip") or recorded_target_ip)
746
847
  new_rules: List[Dict[str, Any]] = []
747
848
  exposed_ports: List[Dict[str, Any]] = []
748
849
  for spec in normalized_specs:
749
- hostname = f"{spec['subdomain']}.{domain}"
850
+ hostname = reusable_hostnames.get((spec["port"], spec["protocol"]))
851
+ if not hostname:
852
+ hostname = _new_container_hostname(
853
+ normalized_device_id,
854
+ spec["port"],
855
+ spec["protocol"],
856
+ len(new_rules),
857
+ tunnel_state,
858
+ )
750
859
  destination = f"{spec['protocol']}://[{normalized_device_id}]:{spec['port']}"
751
- new_rules.append(
752
- {
860
+ new_rule = {
753
861
  "hostname": hostname,
754
862
  "destination": destination,
755
863
  "parsed": _parse_destination(destination),
756
864
  }
757
- )
865
+ if recorded_target_ip:
866
+ new_rule["resolved_ip"] = recorded_target_ip
867
+ new_rules.append(new_rule)
758
868
  exposed_ports.append(
759
869
  {
760
870
  "port": spec["port"],
@@ -816,6 +926,60 @@ def set_container_forwarding_rules(
816
926
  }
817
927
 
818
928
 
929
+ def reconcile_container_forwarding_ip(
930
+ container_device_id: Any,
931
+ *,
932
+ proxmox: Any | None = None,
933
+ node: str | None = None,
934
+ lease_attempts: int = 1,
935
+ lease_retry_seconds: float = 1.0,
936
+ ) -> bool:
937
+ """Refresh ingress after a started exposed container receives a different IP."""
938
+ device_id = str(container_device_id or "").strip()
939
+ if not device_id:
940
+ return False
941
+ tunnel_state = _load_tunnel_state()
942
+ domain = str(tunnel_state.get("domain") or "").strip().lower().rstrip(".")
943
+ with _FORWARDING_UPDATE_LOCK:
944
+ stored = load_forwarding_state().get("rules", [])
945
+ rules = _normalize_rules(stored, domain, from_storage=True)
946
+ target_rules = [rule for rule in rules if _rule_targets_container(rule, device_id, domain)]
947
+ if not target_rules:
948
+ return False
949
+ if proxmox is None or node is None:
950
+ infra_config = _ensure_infra_configured()
951
+ proxmox = _connect_proxmox(infra_config)
952
+ node = _get_node_from_config(infra_config)
953
+ vmid = _resolve_device_vmid(device_id, proxmox, node)
954
+ live_ip = ""
955
+ last_error: Exception | None = None
956
+ for attempt in range(max(1, lease_attempts)):
957
+ try:
958
+ live_ip = _find_container_ip(proxmox, node, vmid, _load_leases())
959
+ break
960
+ except RuntimeError as exc:
961
+ last_error = exc
962
+ if attempt + 1 < max(1, lease_attempts):
963
+ time.sleep(max(0.0, lease_retry_seconds))
964
+ if not live_ip:
965
+ raise RuntimeError(
966
+ f"Unable to resolve live IP after container start: {last_error}"
967
+ )
968
+ recorded_ips = {str(rule.get("resolved_ip") or "") for rule in target_rules}
969
+ if recorded_ips == {live_ip}:
970
+ return False
971
+ logger.info(
972
+ "Exposed container IP changed device_id=%s recorded_ips=%s live_ip=%s; rebuilding ingress",
973
+ device_id,
974
+ sorted(recorded_ips),
975
+ live_ip,
976
+ )
977
+ for rule in target_rules:
978
+ rule["resolved_ip"] = live_ip
979
+ _apply_and_persist_forwarding_rules(rules, tunnel_state=tunnel_state)
980
+ return True
981
+
982
+
819
983
  class CloudflareForwardingHandler(SyncHandler):
820
984
  @property
821
985
  def command_name(self) -> str:
@@ -54,10 +54,9 @@ CACHE_PARENT_MARKER = "cache_parent="
54
54
  _PROVISIONING_MAINTENANCE_LAST_RUN = 0.0
55
55
  PROVISIONING_MAINTENANCE_INTERVAL_S = 300
56
56
  PROXMOX_INVENTORY_RECONCILE_INTERVAL_S = 300
57
- # Keep reusable roots small while leaving enough room for the OS, Portacode,
58
- # Codex, and (on apt systems) Playwright Chromium to be installed. A request
59
- # below this size still builds at its requested size; if the software cannot
60
- # fit, provisioning fails rather than silently giving the user a larger disk.
57
+ # Every distro/storage lineage has one reusable base template of this exact
58
+ # size. Guests may request more space and expand their linked clone, but neither
59
+ # requests nor cache templates may be smaller than the canonical base.
61
60
  PROVISIONING_TEMPLATE_BASE_DISK_GIB = 4
62
61
 
63
62
 
@@ -1045,21 +1044,27 @@ def _pick_container_disk_gib(kind: str, cfg: Dict[str, Any], entry: Dict[str, An
1045
1044
  return 0.0
1046
1045
 
1047
1046
 
1048
- def _to_mib(value: Any) -> float:
1047
+ def _configured_memory_mib(value: Any) -> float:
1049
1048
  try:
1050
1049
  val = float(value)
1051
1050
  except (TypeError, ValueError):
1052
1051
  return 0.0
1053
- if val <= 0:
1054
- return 0.0
1055
- # Heuristic: large values are bytes, smaller ones are already MiB.
1056
- return _bytes_to_mib(val) if val > 10000 else val
1052
+ return val if val > 0 else 0.0
1057
1053
 
1058
1054
 
1059
1055
  def _pick_container_ram_mib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
1060
- for candidate in (cfg.get("memory"), entry.get("maxmem"), entry.get("mem")):
1061
- ram = _to_mib(candidate)
1062
- if ram:
1056
+ # Proxmox config endpoints report `memory` in MiB, while cluster/resource
1057
+ # list fields (`maxmem` and `mem`) are bytes. Do not infer units from the
1058
+ # magnitude: valid configurations commonly exceed 10,000 MiB.
1059
+ configured = _configured_memory_mib(cfg.get("memory"))
1060
+ if configured:
1061
+ return configured
1062
+ for key in ("maxmem", "mem"):
1063
+ value = entry.get(key)
1064
+ if value is None:
1065
+ continue
1066
+ ram = _bytes_to_mib(value)
1067
+ if ram > 0:
1063
1068
  return ram
1064
1069
  return 0.0
1065
1070
 
@@ -1366,20 +1371,77 @@ def reconcile_managed_containers_inventory() -> Dict[str, Any]:
1366
1371
  if not config:
1367
1372
  return {"refreshed": False, "reason": "not_configured"}
1368
1373
  refreshed_summary = _build_full_container_summary(records, config)
1374
+ missing_records = [
1375
+ record
1376
+ for record in records
1377
+ if str(record.get("vmid"))
1378
+ in {
1379
+ str(entry.get("vmid"))
1380
+ for entry in refreshed_summary.get("missing_containers", [])
1381
+ if entry.get("vmid") is not None
1382
+ }
1383
+ ]
1384
+
1369
1385
  refreshed_totals = {
1370
1386
  "ram_mib": int(refreshed_summary.get("total_ram_mib") or 0),
1371
1387
  "disk_gib": int(refreshed_summary.get("total_disk_gib") or 0),
1372
1388
  "cpu_share": float(refreshed_summary.get("total_cpu_share") or 0.0),
1373
1389
  }
1374
1390
 
1391
+ cleaned_vmids: List[int] = []
1392
+ cleanup_errors: List[Dict[str, str]] = []
1375
1393
  with _MANAGED_CONTAINERS_STATE_LOCK:
1376
1394
  if int(_MANAGED_CONTAINERS_STATE.get("revision") or 0) != revision:
1377
1395
  return {"refreshed": False, "reason": "state_changed"}
1396
+ # Serialize cleanup with create/delete state mutations. This prevents a
1397
+ # stale inventory scan from clearing metadata after a CTID is reused.
1398
+ for record in missing_records:
1399
+ try:
1400
+ vmid = int(record["vmid"])
1401
+ except (KeyError, TypeError, ValueError):
1402
+ continue
1403
+ device_id = str(record.get("device_id") or "").strip()
1404
+ try:
1405
+ if device_id:
1406
+ from .cloudflare_forwarding import set_container_forwarding_rules
1407
+
1408
+ try:
1409
+ set_container_forwarding_rules(device_id, [])
1410
+ except RuntimeError as exc:
1411
+ message = str(exc).lower()
1412
+ if (
1413
+ "cloudflare tunnel is not configured yet" not in message
1414
+ and "domain or tunnel name missing" not in message
1415
+ ):
1416
+ raise
1417
+ cleaned_vmids.append(vmid)
1418
+ except Exception as exc: # retry on the next reconciliation pass
1419
+ logger.warning(
1420
+ "Unable to clean Proxmox-side metadata for missing container %s: %s",
1421
+ vmid,
1422
+ exc,
1423
+ )
1424
+ cleanup_errors.append({"vmid": str(vmid), "error": str(exc)})
1425
+ for vmid in cleaned_vmids:
1426
+ path = CONTAINERS_DIR / f"ct-{vmid}.json"
1427
+ path.unlink(missing_ok=True)
1428
+ _MANAGED_CONTAINERS_STATE["records"].pop(str(vmid), None)
1429
+ if cleaned_vmids:
1430
+ cleaned_keys = {str(vmid) for vmid in cleaned_vmids}
1431
+ refreshed_summary["missing_containers"] = [
1432
+ entry
1433
+ for entry in refreshed_summary.get("missing_containers", [])
1434
+ if str(entry.get("vmid")) not in cleaned_keys
1435
+ ]
1378
1436
  _MANAGED_CONTAINERS_STATE["base_summary"] = refreshed_summary
1379
1437
  _MANAGED_CONTAINERS_STATE["initial_totals"] = refreshed_totals
1438
+ if cleaned_vmids:
1439
+ _MANAGED_CONTAINERS_STATE["revision"] = revision + 1
1380
1440
  return {
1381
1441
  "refreshed": True,
1382
1442
  "inventory_updated_at": refreshed_summary.get("inventory_updated_at"),
1443
+ "cleaned_vmids": cleaned_vmids,
1444
+ "cleanup_errors": cleanup_errors,
1383
1445
  }
1384
1446
 
1385
1447
 
@@ -1516,6 +1578,7 @@ def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[st
1516
1578
  allocated_disk += disk_gib
1517
1579
  registry_record = template_registry.get(vmid_str) or {}
1518
1580
  source_hash = _extract_marker_value(description, CACHE_SOURCE_MARKER)
1581
+ resolved_source = registry_record.get("source_template") or source_by_hash.get(source_hash)
1519
1582
  templates.append({
1520
1583
  "type": kind,
1521
1584
  "vmid": vmid_str,
@@ -1525,7 +1588,13 @@ def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[st
1525
1588
  "disk_gib": disk_gib,
1526
1589
  "ram_mib": _pick_container_ram_mib(kind, cfg, entry),
1527
1590
  "cpu_share": _pick_container_cpu_share(kind, cfg, entry),
1528
- "source_template": registry_record.get("source_template") or source_by_hash.get(source_hash),
1591
+ "source_template": resolved_source,
1592
+ "source_hash": registry_record.get("source_hash") or source_hash,
1593
+ "display_name": (
1594
+ f"{_cache_source_display_name(resolved_source or '')} "
1595
+ f"— base {registry_record.get('cache_id') or _extract_marker_value(description, CACHE_ID_MARKER) or 'unknown'} "
1596
+ f"({int(disk_gib)} GiB)"
1597
+ ),
1529
1598
  "cache_id": registry_record.get("cache_id") or _extract_marker_value(description, CACHE_ID_MARKER),
1530
1599
  "cache_status": registry_record.get("status"),
1531
1600
  "is_cache_template": PORTACODE_CACHE_MARKER in description,
@@ -1870,6 +1939,31 @@ def _cache_source_hash(source_template: str) -> str:
1870
1939
  return hashlib.sha256(source_template.encode("utf-8")).hexdigest()[:16]
1871
1940
 
1872
1941
 
1942
+ def _cache_source_display_name(source_template: str) -> str:
1943
+ """Return a concise distro/version label for operators and inventory UI."""
1944
+ filename = str(source_template or "").rsplit("/", 1)[-1]
1945
+ filename = re.sub(r"\.(?:tar\.(?:gz|xz|zst)|tar|tgz|txz)$", "", filename, flags=re.I)
1946
+ filename = re.sub(r"_\d{8}_[a-z0-9]+$", "", filename, flags=re.I)
1947
+ filename = re.sub(r"_\d+(?:\.\d+)*(?:-\d+)?_[a-z0-9.]+$", "", filename, flags=re.I)
1948
+ filename = re.sub(r"-(?:default|standard|turnkey-[a-z0-9-]+)$", "", filename, flags=re.I)
1949
+ words = [part for part in re.split(r"[-_]+", filename) if part]
1950
+ return " ".join(
1951
+ word.capitalize() if not re.match(r"^\d", word) else word
1952
+ for word in words
1953
+ ) or "Unknown distro"
1954
+
1955
+
1956
+ def _cache_template_hostname(source_template: str) -> str:
1957
+ """Human-readable, deterministic Proxmox hostname for the canonical base."""
1958
+ label = _cache_source_display_name(source_template).lower()
1959
+ slug = re.sub(r"[^a-z0-9]+", "-", label).strip("-") or "distro"
1960
+ cache_version = re.sub(r"[^a-z0-9]+", "-", PROVISIONING_CACHE_ID.lower()).strip("-")
1961
+ suffix = f"-v{cache_version}-{PROVISIONING_TEMPLATE_BASE_DISK_GIB}g"
1962
+ prefix = "cache-"
1963
+ max_slug_length = 63 - len(prefix) - len(suffix)
1964
+ return f"{prefix}{slug[:max_slug_length].rstrip('-')}{suffix}"
1965
+
1966
+
1873
1967
  def _cache_lineage_key(source_template: str, storage: str) -> str:
1874
1968
  return f"{_cache_source_hash(source_template)}:{storage}"
1875
1969
 
@@ -1959,8 +2053,8 @@ def _find_native_template(
1959
2053
  and record.get("node") == node
1960
2054
  and record.get("cache_id") == PROVISIONING_CACHE_ID
1961
2055
  and record.get("status") == "ready"
1962
- and int(record.get("disk_gib") or 0) > 0
1963
- and int(record.get("disk_gib") or 0) <= int(requested_disk_gib)
2056
+ and int(record.get("disk_gib") or 0) == PROVISIONING_TEMPLATE_BASE_DISK_GIB
2057
+ and int(requested_disk_gib) >= PROVISIONING_TEMPLATE_BASE_DISK_GIB
1964
2058
  ]
1965
2059
  candidates.sort(key=lambda entry: str(entry.get("created_at") or ""), reverse=True)
1966
2060
  for record in candidates:
@@ -2491,7 +2585,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2491
2585
  "apt": {
2492
2586
  "update_cmd": "apt-get update -y || test $? -eq 100",
2493
2587
  "update_step_name": "apt_update",
2494
- "install_cmd": "apt-get install -y python3 python3-pip python3-venv sudo --fix-missing",
2588
+ "install_cmd": "apt-get install -y python3 python3-pip python3-venv sudo git --fix-missing",
2495
2589
  "install_step_name": "install_deps",
2496
2590
  "update_retries": 4,
2497
2591
  "install_retries": 5,
@@ -2499,7 +2593,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2499
2593
  "dnf": {
2500
2594
  "update_cmd": "dnf check-update || true",
2501
2595
  "update_step_name": "dnf_update",
2502
- "install_cmd": "dnf install -y python3 python3-pip sudo",
2596
+ "install_cmd": "dnf install -y python3 python3-pip sudo git",
2503
2597
  "install_step_name": "install_deps",
2504
2598
  "update_retries": 3,
2505
2599
  "install_retries": 5,
@@ -2507,7 +2601,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2507
2601
  "yum": {
2508
2602
  "update_cmd": "yum makecache",
2509
2603
  "update_step_name": "yum_update",
2510
- "install_cmd": "yum install -y python3 python3-pip sudo",
2604
+ "install_cmd": "yum install -y python3 python3-pip sudo git",
2511
2605
  "install_step_name": "install_deps",
2512
2606
  "update_retries": 3,
2513
2607
  "install_retries": 5,
@@ -2515,7 +2609,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2515
2609
  "apk": {
2516
2610
  "update_cmd": "apk update",
2517
2611
  "update_step_name": "apk_update",
2518
- "install_cmd": "apk add --no-cache python3 py3-pip sudo shadow",
2612
+ "install_cmd": "apk add --no-cache python3 py3-pip sudo shadow git",
2519
2613
  "install_step_name": "install_deps",
2520
2614
  "update_retries": 3,
2521
2615
  "install_retries": 5,
@@ -2523,7 +2617,7 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2523
2617
  "pacman": {
2524
2618
  "update_cmd": "pacman -Sy --noconfirm",
2525
2619
  "update_step_name": "pacman_update",
2526
- "install_cmd": "pacman -S --noconfirm python python-pip sudo",
2620
+ "install_cmd": "pacman -S --noconfirm python python-pip sudo git",
2527
2621
  "install_step_name": "install_deps",
2528
2622
  "update_retries": 3,
2529
2623
  "install_retries": 5,
@@ -2532,10 +2626,10 @@ _PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
2532
2626
  "update_cmd": "zypper --non-interactive --gpg-auto-import-keys refresh",
2533
2627
  "update_step_name": "zypper_update",
2534
2628
  "install_cmd": (
2535
- "zypper --non-interactive install -y python3 python3-pip python3-virtualenv sudo "
2536
- "|| zypper --non-interactive install -y python311 python311-pip python311-virtualenv sudo "
2537
- "|| zypper --non-interactive install -y python311 python311-pip sudo "
2538
- "|| zypper --non-interactive install -y python3 python3-pip sudo"
2629
+ "zypper --non-interactive install -y python3 python3-pip python3-virtualenv sudo git "
2630
+ "|| zypper --non-interactive install -y python311 python311-pip python311-virtualenv sudo git "
2631
+ "|| zypper --non-interactive install -y python311 python311-pip sudo git "
2632
+ "|| zypper --non-interactive install -y python3 python3-pip sudo git"
2539
2633
  ),
2540
2634
  "install_step_name": "install_deps",
2541
2635
  "update_retries": 3,
@@ -2949,6 +3043,10 @@ def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) ->
2949
3043
  bridge = config.get("network", {}).get("bridge", DEFAULT_BRIDGE)
2950
3044
  hostname = (message.get("hostname") or "").strip()
2951
3045
  disk_gib = int(max(round(_validate_positive_number(message.get("disk_gib") or message.get("disk"), 3)), 1))
3046
+ if disk_gib < PROVISIONING_TEMPLATE_BASE_DISK_GIB:
3047
+ raise ValueError(
3048
+ f"disk_gib must be at least {PROVISIONING_TEMPLATE_BASE_DISK_GIB} GiB"
3049
+ )
2952
3050
  ram_mib = int(max(round(_validate_positive_number(message.get("ram_mib") or message.get("ram"), 2048)), 1))
2953
3051
  cpus = _validate_positive_number(message.get("cpus"), 0.2)
2954
3052
  storage = message.get("storage") or config.get("default_storage") or ""
@@ -4366,13 +4464,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
4366
4464
  )
4367
4465
  else:
4368
4466
  builder_payload = dict(payload)
4369
- builder_payload["disk_gib"] = min(
4370
- int(payload["disk_gib"]),
4371
- PROVISIONING_TEMPLATE_BASE_DISK_GIB,
4372
- )
4373
- builder_payload["hostname"] = (
4374
- f"pcache-{_cache_source_hash(source_template)[:8]}-{PROVISIONING_CACHE_ID}"
4375
- )[:63]
4467
+ builder_payload["disk_gib"] = PROVISIONING_TEMPLATE_BASE_DISK_GIB
4468
+ builder_payload["hostname"] = _cache_template_hostname(source_template)
4376
4469
  builder_payload["description"] = _cache_template_description(
4377
4470
  source_template,
4378
4471
  payload["storage"],
@@ -4517,10 +4610,7 @@ class CreateProxmoxContainerHandler(SyncHandler):
4517
4610
  vmid,
4518
4611
  source_template=source_template,
4519
4612
  storage=payload_local["storage"],
4520
- disk_gib=min(
4521
- int(payload_local["disk_gib"]),
4522
- PROVISIONING_TEMPLATE_BASE_DISK_GIB,
4523
- ),
4613
+ disk_gib=PROVISIONING_TEMPLATE_BASE_DISK_GIB,
4524
4614
  )
4525
4615
  _raise_if_device_deletion_requested(device_id)
4526
4616
  builder_vmid = vmid
@@ -4974,6 +5064,25 @@ class StartProxmoxContainerHandler(SyncHandler):
4974
5064
  status, elapsed = _start_container(proxmox, node, vmid)
4975
5065
  _update_container_record(vmid, {"status": "running"})
4976
5066
 
5067
+ forwarding_ip_updated = False
5068
+ try:
5069
+ from .cloudflare_forwarding import reconcile_container_forwarding_ip
5070
+
5071
+ forwarding_ip_updated = reconcile_container_forwarding_ip(
5072
+ child_device_id,
5073
+ proxmox=proxmox,
5074
+ node=node,
5075
+ lease_attempts=6,
5076
+ lease_retry_seconds=1.0,
5077
+ )
5078
+ except Exception:
5079
+ logger.warning(
5080
+ "Unable to reconcile exposed IP after starting container device_id=%s vmid=%s",
5081
+ child_device_id,
5082
+ vmid,
5083
+ exc_info=True,
5084
+ )
5085
+
4977
5086
  infra = get_infra_snapshot()
4978
5087
  return {
4979
5088
  "event": "proxmox_container_action",
@@ -4981,7 +5090,10 @@ class StartProxmoxContainerHandler(SyncHandler):
4981
5090
  "success": True,
4982
5091
  "ctid": str(vmid),
4983
5092
  "message": f"Started container {vmid} in {elapsed:.1f}s.",
4984
- "details": {"exitstatus": status.get("exitstatus")},
5093
+ "details": {
5094
+ "exitstatus": status.get("exitstatus"),
5095
+ "forwarding_ip_updated": forwarding_ip_updated,
5096
+ },
4985
5097
  "status": status.get("status"),
4986
5098
  "infra": infra,
4987
5099
  }