remo-cli 2.2.0rc1__tar.gz → 2.2.0rc3__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 (414) hide show
  1. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.devcontainer/devcontainer.json +2 -2
  2. remo_cli-2.2.0rc3/.specify/feature.json +3 -0
  3. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/CLAUDE.md +7 -4
  4. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/PKG-INFO +14 -6
  5. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/README.md +13 -5
  6. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docker/Dockerfile +26 -7
  7. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docker/compose.example.yml +113 -0
  8. remo_cli-2.2.0rc3/docker/entrypoint.sh +88 -0
  9. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/web-session-interface.md +323 -9
  10. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/api/client.ts +14 -4
  11. remo_cli-2.2.0rc3/frontend/src/components/AppRoot.tsx +25 -0
  12. remo_cli-2.2.0rc3/frontend/src/components/AwaitingAdoption.css +115 -0
  13. remo_cli-2.2.0rc3/frontend/src/components/AwaitingAdoption.tsx +109 -0
  14. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TopBar.tsx +5 -0
  15. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/main.tsx +5 -4
  16. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/health.ts +20 -8
  17. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/pyproject.toml +1 -1
  18. remo_cli-2.2.0rc3/specs/011-web-adopt/checklists/requirements.md +44 -0
  19. remo_cli-2.2.0rc3/specs/011-web-adopt/contracts/cli-web-adopt.md +78 -0
  20. remo_cli-2.2.0rc3/specs/011-web-adopt/contracts/setup-api.md +98 -0
  21. remo_cli-2.2.0rc3/specs/011-web-adopt/data-model.md +123 -0
  22. remo_cli-2.2.0rc3/specs/011-web-adopt/plan.md +163 -0
  23. remo_cli-2.2.0rc3/specs/011-web-adopt/quickstart-results.md +29 -0
  24. remo_cli-2.2.0rc3/specs/011-web-adopt/quickstart.md +135 -0
  25. remo_cli-2.2.0rc3/specs/011-web-adopt/research.md +311 -0
  26. remo_cli-2.2.0rc3/specs/011-web-adopt/spec.md +507 -0
  27. remo_cli-2.2.0rc3/specs/011-web-adopt/tasks.md +213 -0
  28. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/web.py +155 -0
  29. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/ssh.py +45 -2
  30. remo_cli-2.2.0rc3/src/remo_cli/core/web_adopt.py +1338 -0
  31. remo_cli-2.2.0rc3/src/remo_cli/web/api/setup.py +394 -0
  32. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/app.py +40 -1
  33. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/check.py +71 -4
  34. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/config.py +65 -0
  35. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/discovery.py +9 -1
  36. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/health.py +74 -15
  37. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/logging_config.py +16 -0
  38. remo_cli-2.2.0rc3/src/remo_cli/web/state.py +287 -0
  39. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/terminal.py +29 -2
  40. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/image/test_docker_image.py +512 -5
  41. remo_cli-2.2.0rc3/tests/integration/test_web_adopt_e2e.py +736 -0
  42. remo_cli-2.2.0rc3/tests/unit/cli/test_web_adopt_cmd.py +354 -0
  43. remo_cli-2.2.0rc3/tests/unit/core/test_ssh_identity_opts.py +363 -0
  44. remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_authorize.py +346 -0
  45. remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_payload.py +253 -0
  46. remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_trust.py +554 -0
  47. remo_cli-2.2.0rc3/tests/unit/core/test_web_push.py +630 -0
  48. remo_cli-2.2.0rc3/tests/unit/web/conftest.py +188 -0
  49. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_csp.py +51 -0
  50. remo_cli-2.2.0rc3/tests/unit/web/test_health_states.py +304 -0
  51. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_log_redaction.py +39 -0
  52. remo_cli-2.2.0rc3/tests/unit/web/test_setup_api.py +484 -0
  53. remo_cli-2.2.0rc3/tests/unit/web/test_setup_auth.py +378 -0
  54. remo_cli-2.2.0rc3/tests/unit/web/test_state.py +292 -0
  55. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/uv.lock +1 -1
  56. remo_cli-2.2.0rc1/.specify/feature.json +0 -3
  57. remo_cli-2.2.0rc1/docker/entrypoint.sh +0 -29
  58. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-analyze/SKILL.md +0 -0
  59. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-checklist/SKILL.md +0 -0
  60. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-clarify/SKILL.md +0 -0
  61. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-constitution/SKILL.md +0 -0
  62. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-converge/SKILL.md +0 -0
  63. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
  64. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
  65. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
  66. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
  67. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
  68. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-implement/SKILL.md +0 -0
  69. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-plan/SKILL.md +0 -0
  70. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-specify/SKILL.md +0 -0
  71. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-tasks/SKILL.md +0 -0
  72. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
  73. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.dockerignore +0 -0
  74. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/dependabot.yml +0 -0
  75. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/ci.yml +0 -0
  76. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/provision.yml +0 -0
  77. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/release.yml +0 -0
  78. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/smoke-test-cleanup.yml +0 -0
  79. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/smoke-test.yml +0 -0
  80. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/teardown.yml +0 -0
  81. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.gitignore +0 -0
  82. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
  83. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
  84. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/validation_complete.json +0 -0
  85. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/.registry +0 -0
  86. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/README.md +0 -0
  87. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
  88. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
  89. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
  90. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
  91. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
  92. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/config-template.yml +0 -0
  93. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/extension.yml +0 -0
  94. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/git-config.yml +0 -0
  95. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
  96. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
  97. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
  98. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
  99. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
  100. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
  101. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
  102. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
  103. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions.yml +0 -0
  104. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/init-options.json +0 -0
  105. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integration.json +0 -0
  106. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integrations/claude.manifest.json +0 -0
  107. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integrations/speckit.manifest.json +0 -0
  108. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/memory/constitution.md +0 -0
  109. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  110. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/common.sh +0 -0
  111. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/create-new-feature.sh +0 -0
  112. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/setup-plan.sh +0 -0
  113. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/setup-tasks.sh +0 -0
  114. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/update-agent-context.sh +0 -0
  115. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/agent-file-template.md +0 -0
  116. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/checklist-template.md +0 -0
  117. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/constitution-template.md +0 -0
  118. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/plan-template.md +0 -0
  119. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/spec-template.md +0 -0
  120. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/tasks-template.md +0 -0
  121. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/workflows/speckit/workflow.yml +0 -0
  122. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/workflows/workflow-registry.json +0 -0
  123. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/AGENTS.md +0 -0
  124. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/CONTRIBUTING.md +0 -0
  125. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/Dockerfile +0 -0
  126. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/LICENSE +0 -0
  127. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/.ansible-lint +0 -0
  128. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/README.md +0 -0
  129. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/ansible.cfg +0 -0
  130. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_configure.yml +0 -0
  131. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_resize.yml +0 -0
  132. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_site.yml +0 -0
  133. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_teardown.yml +0 -0
  134. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/all.yml +0 -0
  135. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/incus_containers.yml +0 -0
  136. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/proxmox_containers.yml +0 -0
  137. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_configure.yml +0 -0
  138. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_provision.yml +0 -0
  139. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_resize.yml +0 -0
  140. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_site.yml +0 -0
  141. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_teardown.yml +0 -0
  142. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_bootstrap.yml +0 -0
  143. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_configure.yml +0 -0
  144. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_provision.yml +0 -0
  145. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_resize.yml +0 -0
  146. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_site.yml +0 -0
  147. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_teardown.yml +0 -0
  148. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/inventory/hosts.yml +0 -0
  149. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/inventory/incus_containers.yml +0 -0
  150. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_bootstrap.yml +0 -0
  151. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_configure.yml +0 -0
  152. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_provision.yml +0 -0
  153. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_resize.yml +0 -0
  154. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_site.yml +0 -0
  155. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_teardown.yml +0 -0
  156. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/requirements.yml +0 -0
  157. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/defaults/main.yml +0 -0
  158. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
  159. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
  160. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/main.yml +0 -0
  161. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
  162. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
  163. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
  164. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/deacon/defaults/main.yml +0 -0
  165. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/deacon/tasks/main.yml +0 -0
  166. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/devcontainers/tasks/main.yml +0 -0
  167. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/docker/handlers/main.yml +0 -0
  168. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/docker/tasks/main.yml +0 -0
  169. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/fzf/tasks/main.yml +0 -0
  170. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/github_cli/tasks/main.yml +0 -0
  171. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
  172. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
  173. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
  174. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
  175. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
  176. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/defaults/main.yml +0 -0
  177. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/handlers/main.yml +0 -0
  178. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/meta/main.yml +0 -0
  179. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/tasks/main.yml +0 -0
  180. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
  181. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
  182. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
  183. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
  184. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/nodejs/defaults/main.yml +0 -0
  185. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/nodejs/tasks/main.yml +0 -0
  186. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
  187. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
  188. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
  189. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
  190. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
  191. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/meta/main.yml +0 -0
  192. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
  193. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
  194. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
  195. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
  196. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
  197. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/defaults/main.yml +0 -0
  198. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/tasks/main.yml +0 -0
  199. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
  200. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
  201. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
  202. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
  203. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/defaults/main.yml +0 -0
  204. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/tasks/main.yml +0 -0
  205. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
  206. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
  207. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/tasks/configure_dev_tools.yml +0 -0
  208. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/aws.md +0 -0
  209. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/hetzner.md +0 -0
  210. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/incus.md +0 -0
  211. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/install.sh +0 -0
  212. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/proxmox.md +0 -0
  213. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/remo-web-speckit-input.md +0 -0
  214. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/remo-web.html +0 -0
  215. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/index.html +0 -0
  216. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/package-lock.json +0 -0
  217. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/package.json +0 -0
  218. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/README.md +0 -0
  219. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/dist__ghostty-vt.wasm +0 -0
  220. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/ghostty-vt.wasm +0 -0
  221. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
  222. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/AppShell.css +0 -0
  223. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/AppShell.tsx +0 -0
  224. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/OfflineOverlay.css +0 -0
  225. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/OfflineOverlay.tsx +0 -0
  226. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SessionRail.css +0 -0
  227. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SessionRail.tsx +0 -0
  228. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SettingsPage.css +0 -0
  229. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SettingsPage.tsx +0 -0
  230. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/ShortcutsModal.css +0 -0
  231. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/ShortcutsModal.tsx +0 -0
  232. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TerminalCard.css +0 -0
  233. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TerminalCard.tsx +0 -0
  234. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TopBar.css +0 -0
  235. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/WorkspacePane.css +0 -0
  236. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/WorkspacePane.tsx +0 -0
  237. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/providerMeta.ts +0 -0
  238. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/railModel.ts +0 -0
  239. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/discovery.ts +0 -0
  240. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/fonts.ts +0 -0
  241. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/settings.ts +0 -0
  242. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/useConsoleKeyboard.ts +0 -0
  243. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/workspace.ts +0 -0
  244. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/.gitkeep +0 -0
  245. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
  246. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/RendererAdapter.ts +0 -0
  247. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/TerminalConnection.ts +0 -0
  248. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/XtermRenderer.ts +0 -0
  249. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/defaultRenderer.ts +0 -0
  250. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/theme/fonts.ts +0 -0
  251. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/theme/tokens.css +0 -0
  252. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/tsconfig.json +0 -0
  253. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/vite.config.ts +0 -0
  254. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/install.sh +0 -0
  255. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/maverick.yaml +0 -0
  256. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/playwright.config.ts +0 -0
  257. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/project-menu.sh +0 -0
  258. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/remo +0 -0
  259. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/run.sh +0 -0
  260. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/scripts/setup-aws-oidc.sh +0 -0
  261. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
  262. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
  263. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
  264. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
  265. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/data-model.md +0 -0
  266. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/plan.md +0 -0
  267. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
  268. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/research.md +0 -0
  269. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/spec.md +0 -0
  270. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/tasks.md +0 -0
  271. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/checklists/requirements.md +0 -0
  272. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
  273. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
  274. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/data-model.md +0 -0
  275. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/plan.md +0 -0
  276. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/quickstart.md +0 -0
  277. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/research.md +0 -0
  278. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/spec.md +0 -0
  279. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/tasks.md +0 -0
  280. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
  281. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/data-model.md +0 -0
  282. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/plan.md +0 -0
  283. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/quickstart.md +0 -0
  284. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/research.md +0 -0
  285. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/spec.md +0 -0
  286. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/tasks.md +0 -0
  287. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/004-proxmox-container-support/spec.md +0 -0
  288. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
  289. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
  290. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/data-model.md +0 -0
  291. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/plan.md +0 -0
  292. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/quickstart.md +0 -0
  293. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/research.md +0 -0
  294. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/spec.md +0 -0
  295. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/tasks.md +0 -0
  296. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/checklists/requirements.md +0 -0
  297. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
  298. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
  299. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
  300. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/data-model.md +0 -0
  301. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/plan.md +0 -0
  302. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/quickstart.md +0 -0
  303. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/research.md +0 -0
  304. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/spec.md +0 -0
  305. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/tasks.md +0 -0
  306. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/__init__.py +0 -0
  307. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/__main__.py +0 -0
  308. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/__init__.py +0 -0
  309. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/cp.py +0 -0
  310. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/main.py +0 -0
  311. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/__init__.py +0 -0
  312. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/aws.py +0 -0
  313. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/hetzner.py +0 -0
  314. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/incus.py +0 -0
  315. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/proxmox.py +0 -0
  316. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/shell.py +0 -0
  317. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/__init__.py +0 -0
  318. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/ansible_runner.py +0 -0
  319. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/completion.py +0 -0
  320. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/config.py +0 -0
  321. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/known_hosts.py +0 -0
  322. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/output.py +0 -0
  323. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/picker.py +0 -0
  324. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/remo_host_client.py +0 -0
  325. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/rsync.py +0 -0
  326. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/snapshot.py +0 -0
  327. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/validation.py +0 -0
  328. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/version.py +0 -0
  329. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/__init__.py +0 -0
  330. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/capability.py +0 -0
  331. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/discovery.py +0 -0
  332. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/host.py +0 -0
  333. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/session_target.py +0 -0
  334. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/snapshot.py +0 -0
  335. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/__init__.py +0 -0
  336. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/aws.py +0 -0
  337. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/hetzner.py +0 -0
  338. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/incus.py +0 -0
  339. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/proxmox.py +0 -0
  340. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/__init__.py +0 -0
  341. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/__init__.py +0 -0
  342. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/hosts.py +0 -0
  343. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/terminals.py +0 -0
  344. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/models.py +0 -0
  345. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/ssh_master.py +0 -0
  346. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/terminal_registry.py +0 -0
  347. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/tokens.py +0 -0
  348. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/__init__.py +0 -0
  349. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/ansible/__init__.py +0 -0
  350. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/ansible/test_remo_host_idempotency.py +0 -0
  351. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/conftest.py +0 -0
  352. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/fixtures.ts +0 -0
  353. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
  354. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
  355. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/keyboard-routing.spec.ts +0 -0
  356. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/mobile.spec.ts +0 -0
  357. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/reconnect.spec.ts +0 -0
  358. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/workspace-layout.spec.ts +0 -0
  359. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/image/__init__.py +0 -0
  360. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/__init__.py +0 -0
  361. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/_nine_terminal_fixture.py +0 -0
  362. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/orbstack-cloud-init.yml +0 -0
  363. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/orbstack.sh +0 -0
  364. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_nine_terminals.py +0 -0
  365. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_nine_terminals_soak.py +0 -0
  366. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_remo_host_e2e.py +0 -0
  367. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_security_rejections.py +0 -0
  368. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_terminal_attach.py +0 -0
  369. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_web_cli_parity.py +0 -0
  370. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/perf/__init__.py +0 -0
  371. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/perf/test_latency.py +0 -0
  372. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/__init__.py +0 -0
  373. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/__init__.py +0 -0
  374. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
  375. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
  376. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
  377. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
  378. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_cp.py +0 -0
  379. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_main.py +0 -0
  380. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_shell.py +0 -0
  381. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/__init__.py +0 -0
  382. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ansible_runner.py +0 -0
  383. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_config.py +0 -0
  384. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_known_hosts.py +0 -0
  385. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_output.py +0 -0
  386. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_picker.py +0 -0
  387. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_remo_host_client.py +0 -0
  388. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_rsync.py +0 -0
  389. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_snapshot.py +0 -0
  390. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ssh.py +0 -0
  391. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ssh_controlpath.py +0 -0
  392. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_validation.py +0 -0
  393. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_version.py +0 -0
  394. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/__init__.py +0 -0
  395. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_capability.py +0 -0
  396. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_discovery.py +0 -0
  397. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_session_target.py +0 -0
  398. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/__init__.py +0 -0
  399. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_aws_snapshot.py +0 -0
  400. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
  401. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_incus_snapshot.py +0 -0
  402. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
  403. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
  404. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/test_ansible_templates.py +0 -0
  405. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/test_host_model.py +0 -0
  406. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/__init__.py +0 -0
  407. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_backpressure.py +0 -0
  408. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_check.py +0 -0
  409. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_discovery.py +0 -0
  410. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_hosts_mapping.py +0 -0
  411. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_lazy_import.py +0 -0
  412. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_terminal_resize.py +0 -0
  413. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_terminals_api.py +0 -0
  414. {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_tokens.py +0 -0
@@ -16,7 +16,7 @@
16
16
  "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {},
17
17
  "ghcr.io/devcontainers-extra/features/starship:1": {},
18
18
  "ghcr.io/get2knowio/devcontainer-features/ai-clis:2": {
19
- "install": "claudeCode,specifyCli"
19
+ "install": "claudeCode,specifyCli,copilot,codex"
20
20
  },
21
21
  "ghcr.io/get2knowio/devcontainer-features/modern-cli-tools:2": {},
22
22
  "ghcr.io/get2knowio/devcontainer-features/python-tools:2": {
@@ -41,7 +41,7 @@
41
41
  "source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached",
42
42
  "source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind,consistency=cached",
43
43
  "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached",
44
- "source=${localWorkspaceFolder}/../site,target=/workspaces/site,type=bind,consistency=cached",
44
+ "source=${localWorkspaceFolder}/../site,target=/workspaces/site,type=bind,consistency=cached"
45
45
  ],
46
46
  "postCreateCommand": "python3 --version && node --version"
47
47
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "feature_directory": "specs/011-web-adopt"
3
+ }
@@ -13,6 +13,7 @@ See `.specify/memory/constitution.md` for project principles and non-negotiable
13
13
  - Flat file (`~/.config/remo/known_hosts`, colon-delimited) (003-python-cli-rewrite)
14
14
  - Cross-provider snapshot model (`models/snapshot.py`) + shared helpers in `core/snapshot.py` (name generator, validator, table formatter, destroy-time cleanup hook). No new runtime deps. (005-provider-snapshots)
15
15
  - FastAPI/Uvicorn + WebSockets (backend, optional `web` extra), TypeScript/Vite/React + ghostty-web (frontend), Bash (`remo-host` host command templated by Ansible) (010-web-session-interface)
16
+ - Stdlib `urllib.request` CLI setup client + token-gated `/api/v1/setup/*` FastAPI surface; service state in flat files under the writable `REMO_HOME` volume (`web-identity/` keypair + service known_hosts, `~/.config/remo/web-service.json` saved credentials) (011-web-adopt)
16
17
 
17
18
  - Ansible 2.14+ / YAML + `ansible.builtin`, `community.general` (for zypper module) (001-bootstrap-incus-host)
18
19
 
@@ -27,7 +28,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
27
28
  │ ├── shell.py # remo shell
28
29
  │ ├── cp.py # remo cp
29
30
  │ ├── init_cmd.py # remo init
30
- │ ├── web.py # remo web {serve,check} — lazy-imports remo_cli.web.* (NFR-008)
31
+ │ ├── web.py # remo web {serve,check,adopt,push} — serve/check lazy-import remo_cli.web.* (NFR-008); adopt/push use core/web_adopt only
31
32
  │ └── providers/ # Provider CLI groups
32
33
  │ ├── incus.py # remo incus {create,destroy,update,list,sync,bootstrap}
33
34
  │ ├── hetzner.py # remo hetzner {create,destroy,update,list,sync}
@@ -45,6 +46,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
45
46
  │ ├── known_hosts.py # Flat-file host registry
46
47
  │ ├── ssh.py # build_ssh_base_cmd(), SSH options, terminal reset, timezone
47
48
  │ ├── remo_host_client.py # Versioned remo-host protocol client (shared by CLI + web)
49
+ │ ├── web_adopt.py # Workstation-side adoption/push engine (stdlib HTTP, keyscan trust verify, authorized_keys mgmt, --via tunnel)
48
50
  │ ├── ansible_runner.py # Ansible playbook subprocess
49
51
  │ ├── picker.py # InquirerPy fuzzy picker
50
52
  │ ├── rsync.py # File transfer
@@ -52,7 +54,8 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
52
54
  │ └── init.py # remo init logic
53
55
  ├── web/ # remo-web service — FastAPI; optional `web` extra, lazily imported
54
56
  │ ├── app.py # FastAPI factory: routers, Host/Origin+CSP middleware, serves built SPA
55
- │ ├── config.py # WebSettings (REMO_WEB_* env vars, see docs/web-session-interface.md)
57
+ │ ├── config.py # WebSettings (REMO_WEB_* env vars incl. api_token, see docs/web-session-interface.md)
58
+ │ ├── state.py # ConfigurationState detection (unconfigured/adopted/mount_configured/broken) + service identity generation
56
59
  │ ├── discovery.py # Concurrent per-instance discovery via remo-host + SSH
57
60
  │ ├── ssh_master.py # Per-instance SSH ControlMaster lifecycle
58
61
  │ ├── terminal.py # PTY + `ssh -tt … remo-host sessions attach`, resize/backpressure
@@ -64,6 +67,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
64
67
  │ ├── models.py # Service-only entities: TerminalAttachment, WsToken, SshMaster
65
68
  │ └── api/
66
69
  │ ├── hosts.py # GET /api/v1/hosts, /sessions, POST /discovery/refresh
70
+ │ ├── setup.py # Token-gated /api/v1/setup/{status,identity,registry,verify} (011-web-adopt)
67
71
  │ └── terminals.py # POST/GET/DELETE /api/v1/terminals, WS /api/v1/terminals/{id}
68
72
  └── models/
69
73
  ├── host.py # KnownHost dataclass
@@ -182,10 +186,9 @@ Provider SDKs (boto3, hcloud) are lazy-imported with clear error messages if mis
182
186
  - Ansible 2.14+ / YAML: Follow standard conventions plus Constitution principles
183
187
 
184
188
  ## Recent Changes
189
+ - 011-web-adopt: Added CLI-to-web adoption — unconfigured boot with service-scoped ed25519 identity, token-gated `/api/v1/setup/*` (REMO_WEB_API_TOKEN, fail-closed), `remo web adopt`/`remo web push` (registry mirror + workstation-verified host keys + idempotent `remo-web@<id>` authorized_keys entries), AwaitingAdoption SPA page.
185
190
  - 010-web-session-interface: Added remo-web Docker service (FastAPI + React/ghostty-web) brokering browser terminal sessions across all Remo-managed instances via a new remo-host SSH command; web extra + remo web {serve,check} CLI group.
186
191
  - 005-provider-snapshots: Added cross-provider snapshot CLI (`remo <P> snapshot {create,list,restore,delete}`) + destroy-time cleanup hook across Incus / Proxmox / AWS / Hetzner.
187
- - 003-python-cli-rewrite: Added Python 3.11+ + Click (CLI framework), InquirerPy (interactive picker), boto3 (AWS, optional), hcloud (Hetzner, optional)
188
- - 002-incus-container-support: Added Ansible 2.14+ / YAML + `ansible.builtin`, `community.general` (existing), Incus CLI (local)
189
192
 
190
193
 
191
194
  <!-- MANUAL ADDITIONS START -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remo-cli
3
- Version: 2.2.0rc1
3
+ Version: 2.2.0rc3
4
4
  Summary: Remote development environment CLI
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -357,11 +357,19 @@ uv run remo web serve --host 127.0.0.1 --port 8080 # local dev
357
357
  ```
358
358
 
359
359
  For a home-lab install, use Docker Compose — see [`docker/compose.example.yml`](docker/compose.example.yml)
360
- for a ready-to-adapt file (read-only registry + SSH material mounts, tmpfs runtime dir, healthcheck,
361
- non-root/read-only hardening).
362
-
363
- Full architecture, security model, Compose walkthrough, credentials/SSM setup, discovery states,
364
- terminal limits, troubleshooting, and upgrade notes: **[docs/web-session-interface.md](docs/web-session-interface.md)**.
360
+ for a ready-to-adapt file covering both deployment modes (tmpfs runtime dir, healthcheck,
361
+ non-root/read-only hardening in either case):
362
+
363
+ - **Bind-mount mode**: mount your existing registry and SSH key read-only — the container runs with
364
+ your identity, on the same box as your config.
365
+ - **Adopted mode**: mount nothing. The container generates its own SSH identity in a writable state
366
+ volume, and a single `remo web adopt` from your workstation pushes your registry and authorizes
367
+ that identity on every instance — your personal private key never leaves the workstation
368
+ (`remo web push` re-syncs later changes).
369
+
370
+ Full architecture, security model, Compose walkthrough, adoption workflow, credentials/SSM setup,
371
+ discovery states, terminal limits, troubleshooting, and upgrade notes:
372
+ **[docs/web-session-interface.md](docs/web-session-interface.md)**.
365
373
 
366
374
  ---
367
375
 
@@ -331,11 +331,19 @@ uv run remo web serve --host 127.0.0.1 --port 8080 # local dev
331
331
  ```
332
332
 
333
333
  For a home-lab install, use Docker Compose — see [`docker/compose.example.yml`](docker/compose.example.yml)
334
- for a ready-to-adapt file (read-only registry + SSH material mounts, tmpfs runtime dir, healthcheck,
335
- non-root/read-only hardening).
336
-
337
- Full architecture, security model, Compose walkthrough, credentials/SSM setup, discovery states,
338
- terminal limits, troubleshooting, and upgrade notes: **[docs/web-session-interface.md](docs/web-session-interface.md)**.
334
+ for a ready-to-adapt file covering both deployment modes (tmpfs runtime dir, healthcheck,
335
+ non-root/read-only hardening in either case):
336
+
337
+ - **Bind-mount mode**: mount your existing registry and SSH key read-only — the container runs with
338
+ your identity, on the same box as your config.
339
+ - **Adopted mode**: mount nothing. The container generates its own SSH identity in a writable state
340
+ volume, and a single `remo web adopt` from your workstation pushes your registry and authorizes
341
+ that identity on every instance — your personal private key never leaves the workstation
342
+ (`remo web push` re-syncs later changes).
343
+
344
+ Full architecture, security model, Compose walkthrough, adoption workflow, credentials/SSM setup,
345
+ discovery states, terminal limits, troubleshooting, and upgrade notes:
346
+ **[docs/web-session-interface.md](docs/web-session-interface.md)**.
339
347
 
340
348
  ---
341
349
 
@@ -60,8 +60,14 @@ WORKDIR /app
60
60
  # curl - AWS CLI/SSM plugin install below, and the container
61
61
  # healthcheck (compose.example.yml) at runtime
62
62
  # unzip - AWS CLI v2 install below
63
+ # gosu - drops from root to the unprivileged `remo` user in the
64
+ # entrypoint AFTER self-healing filesystem permissions
65
+ # (see docker/entrypoint.sh). Unlike `su`, it stays PID 1
66
+ # with correct signal semantics and adds no setuid-binary
67
+ # surface. Packaged for both amd64 and arm64 in Debian, so
68
+ # the multi-arch build is unaffected.
63
69
  RUN apt-get update && \
64
- apt-get install -y --no-install-recommends openssh-client curl unzip && \
70
+ apt-get install -y --no-install-recommends openssh-client curl unzip gosu && \
65
71
  rm -rf /var/lib/apt/lists/*
66
72
 
67
73
  # Install AWS CLI v2 (needed for SSM proxy), arch-selected via $TARGETARCH.
@@ -85,10 +91,10 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then SM_ARCH="arm64"; else SM_ARCH="64bit";
85
91
  # Non-root user (UID/GID 1000 to match compose.example.yml `user: "1000:1000"`).
86
92
  RUN useradd -u 1000 -m -s /bin/bash remo
87
93
 
88
- # Install remo-cli[web] system-wide (as root, before USER remo) from the
89
- # wheel built in stage 2 — avoids needing build tools in this final layer
90
- # and avoids `pip install --user` (so no PATH fix-up is needed; `remo` /
91
- # `remo web` land directly on the system PATH for the `remo` user).
94
+ # Install remo-cli[web] system-wide (as root) from the wheel built in stage 2
95
+ # — avoids needing build tools in this final layer and avoids `pip install
96
+ # --user` (so no PATH fix-up is needed; `remo` / `remo web` land directly on
97
+ # the system PATH for the `remo` user the entrypoint drops to).
92
98
  COPY --from=wheel-builder /build/wheels/*.whl /tmp/
93
99
  RUN pip install --no-cache-dir "$(ls /tmp/*.whl)[web]" && \
94
100
  rm /tmp/*.whl
@@ -118,6 +124,19 @@ ENV REMO_WEB_FRONTEND_DIST_DIR=/app/frontend-dist
118
124
  # default, FR-052), same as any other containerized service.
119
125
  ENV REMO_WEB_BIND_HOST=0.0.0.0
120
126
 
121
- USER remo
122
-
127
+ # Pin HOME to the `remo` user's home so config-path resolution
128
+ # (core/config.py `Path.home()` / REMO_HOME) is identical whether the
129
+ # entrypoint is running its root-side healing pass or the dropped-to `remo`
130
+ # process — otherwise root's ambient HOME (/root) would resolve the config
131
+ # dir to a different path than the app later reads.
132
+ ENV HOME=/home/remo
133
+
134
+ # NOTE: intentionally NO `USER remo` here. The image starts as root so the
135
+ # entrypoint can self-heal ownership on a bind-mounted, root-owned config dir
136
+ # and on the /run/remo-ssh tmpfs (which a container *restart* remounts
137
+ # root-owned) BEFORE dropping — via gosu — to the unprivileged `remo` user.
138
+ # The app still ends up running as non-root `remo`; the drop just happens at
139
+ # runtime in the entrypoint instead of at image-build time. A deployer that
140
+ # sets an explicit `--user`/`user:` starts non-root, and the entrypoint skips
141
+ # healing (best-effort) and execs directly. See docker/entrypoint.sh.
123
142
  ENTRYPOINT ["/app/docker/entrypoint.sh"]
@@ -117,3 +117,116 @@ services:
117
117
  retries: 3
118
118
  start_period: 10s
119
119
  restart: unless-stopped
120
+
121
+ # ---------------------------------------------------------------------------
122
+ # Adopted mode (011-web-adopt) — an ALTERNATIVE to `remo-web` above; run one
123
+ # or the other, not both (they publish the same host port).
124
+ #
125
+ # When to use which:
126
+ #
127
+ # - `remo-web` (above, bind-mount mode): you are happy to mount your
128
+ # existing registry and SSH material read-only into the container. The
129
+ # service boots fully configured with YOUR key; nothing else to do.
130
+ # Upgrading to this release changes nothing about that mode (SC-005).
131
+ #
132
+ # - `remo-web-adopted` (below): nothing from your workstation is mounted.
133
+ # The service boots UNCONFIGURED with an empty named state volume,
134
+ # generates its own service-scoped SSH identity there on first start
135
+ # (under web-identity/, comment `remo-web@<deployment-id>`), and waits
136
+ # in a stable "awaiting adoption" state (GET /api/v1/ready -> 200
137
+ # `{"status": "unconfigured"}`). Complete setup from a workstation with
138
+ # a working remo CLI:
139
+ #
140
+ # remo web adopt http://<docker-host>:8080
141
+ #
142
+ # which pushes your registry + instance host keys and authorizes the
143
+ # service's OWN public key on each instance — your personal private key
144
+ # never leaves the workstation. Later registry changes are re-pushed
145
+ # with `remo web push`.
146
+ #
147
+ # This service carries `profiles: ["adopted"]` so a plain `docker compose
148
+ # up` still starts only the bind-mount service above. Start this one with:
149
+ #
150
+ # docker compose --profile adopted up -d
151
+ remo-web-adopted:
152
+ profiles: ["adopted"]
153
+ # Same image (and the same optional `build:` override) as `remo-web`
154
+ # above — the mode is chosen purely by what is mounted, not by the image.
155
+ image: ghcr.io/get2knowio/remo-web:latest
156
+ ports:
157
+ # Adoption traffic (the token-gated /api/v1/setup/* calls made by
158
+ # `remo web adopt`) arrives over this same port. Loopback-only means
159
+ # the workstation running the CLI must be the Docker host itself —
160
+ # otherwise bind a LAN IP here deliberately, or keep loopback and run
161
+ # `remo web adopt --via <docker-host>` to tunnel over SSH.
162
+ - "127.0.0.1:8080:8080"
163
+ environment:
164
+ # REQUIRED for adopted mode: while this is unset/empty the setup API
165
+ # does not exist at all (every /api/v1/setup/* request gets a plain
166
+ # 404), so adoption is impossible. Generate a strong random value,
167
+ # e.g.:
168
+ #
169
+ # openssl rand -hex 24
170
+ #
171
+ # and pass the same value to the CLI via $REMO_API_TOKEN (or the
172
+ # interactive prompt) when running `remo web adopt`.
173
+ - "REMO_WEB_API_TOKEN=replace-me--generate-with--openssl-rand-hex-24"
174
+ # The REMO_WEB_ALLOWED_HOSTS / REMO_WEB_ALLOWED_ORIGINS notes on the
175
+ # service above apply here unchanged.
176
+ volumes:
177
+ # A writable named state volume at REMO_HOME — the ONLY volume in this
178
+ # mode (no registry mount, no ~/.ssh mounts; presence of those is what
179
+ # puts the service in mount-configured mode instead). It holds the
180
+ # pushed registry, per-instance host keys, and the service identity
181
+ # keypair. Deleting the volume resets the service to unconfigured with
182
+ # a NEW identity on next boot; it revokes nothing on the instances —
183
+ # remove the old `remo-web@<deployment-id>` authorized_keys line there.
184
+ - "remo-web-state:/home/remo/.config/remo"
185
+ # A PLAIN, option-less tmpfs — no `rw,mode=1777` pin needed anymore. The
186
+ # image starts as root and its entrypoint re-heals ownership on
187
+ # /run/remo-ssh on EVERY start, so the well-known Docker quirk (an
188
+ # option-less tmpfs is remounted root-owned 0755 by a container *restart*)
189
+ # no longer breaks the startup runtime-dir check on the second boot.
190
+ tmpfs:
191
+ - "/run/remo-ssh"
192
+ read_only: true
193
+ # NOTE: no `user:` pin here (unlike the bind-mount service above). This
194
+ # service starts as ROOT so the entrypoint can self-heal ownership on the
195
+ # state volume (Docker seeds a fresh named volume root-owned; a bind-mount
196
+ # host dir is root-owned too) before dropping — via gosu — to the
197
+ # unprivileged `remo` user. The app STILL runs as non-root `remo`; only
198
+ # the brief healing pass runs as root. See docker/entrypoint.sh.
199
+ security_opt:
200
+ - "no-new-privileges:true"
201
+ # Drop everything, then add back ONLY the capabilities the heal-then-drop
202
+ # entrypoint needs while it is root: CHOWN (fix ownership of the state
203
+ # dir/tmpfs), DAC_OVERRIDE (traverse/write a dir owned by another uid),
204
+ # FOWNER (defensive — chmod/utime on files owned by another uid),
205
+ # SETUID+SETGID (gosu drops to the `remo` uid).
206
+ # The dropped-to `remo` process holds NONE of these effectively — dropping
207
+ # uid clears the permitted/effective sets — so the security posture is
208
+ # unchanged from a plain non-root run; `no-new-privileges` does not block
209
+ # gosu (dropping privileges is always allowed).
210
+ cap_drop:
211
+ - "ALL"
212
+ cap_add:
213
+ - "CHOWN"
214
+ - "DAC_OVERRIDE"
215
+ - "FOWNER"
216
+ - "SETUID"
217
+ - "SETGID"
218
+ healthcheck:
219
+ # /api/v1/ready answers 200 for BOTH `ready` and `unconfigured` — a
220
+ # container awaiting adoption is healthy and must not crash-loop or
221
+ # flap the healthcheck while it waits (SC-006).
222
+ test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/api/v1/ready"]
223
+ interval: 30s
224
+ timeout: 5s
225
+ retries: 3
226
+ start_period: 10s
227
+ restart: unless-stopped
228
+
229
+ volumes:
230
+ # Adopted-mode state (see `remo-web-adopted`). Created on first
231
+ # `--profile adopted` start; harmless if you only use bind-mount mode.
232
+ remo-web-state:
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # docker/entrypoint.sh
3
+ #
4
+ # Container entrypoint for `remo web` (010-web-session-interface, US4;
5
+ # 011-web-adopt).
6
+ #
7
+ # Self-healing permissions model ("start as root, fix perms, drop
8
+ # privileges"). The image starts as root (no `USER remo` in the Dockerfile)
9
+ # so this script can, when it is UID 0, repair ownership on:
10
+ #
11
+ # * the config dir ($REMO_HOME, default $HOME/.config/remo) — a deployer
12
+ # that BIND-MOUNTS this dir gets it owned root:root, so the non-root
13
+ # `remo` process could not create web-identity/ there (adopted mode,
14
+ # 011-web-adopt) and would fail with EACCES on first boot. Docker seeds a
15
+ # fresh NAMED volume root-owned too, so the same fix covers that path.
16
+ # * the SSH ControlMaster runtime dir (/run/remo-ssh) — an option-less
17
+ # tmpfs is remounted root-owned 0755 (unwritable to `remo`) by a container
18
+ # RESTART, which otherwise fails the startup runtime-dir check on every
19
+ # boot after the first. Re-healing it on EVERY start makes restarts behave
20
+ # like the first boot without the deployer pinning `rw,mode=1777`.
21
+ #
22
+ # After healing, it drops to the unprivileged `remo` user via `gosu` and
23
+ # re-execs itself; that second pass takes the non-root branch below (the gate
24
+ # + serve). `gosu` keeps us as PID 1 with correct signal semantics (SIGTERM
25
+ # reaches uvicorn for graceful shutdown) and, unlike a setuid `su`, adds no
26
+ # privilege-escalation surface — compatible with `no-new-privileges:true`.
27
+ #
28
+ # Healing is BEST-EFFORT and never hard-fails startup: a read-only config
29
+ # mount (mount-configured mode) legitimately cannot be chowned, and a deployer
30
+ # may drop CAP_CHOWN. `remo web check` (below) is the real gate on whether the
31
+ # resulting filesystem is actually usable. If the container is started with an
32
+ # explicit non-root `--user`/`user:`, this script is never UID 0, so it skips
33
+ # healing entirely and just runs the gate + serve.
34
+ #
35
+ # The `remo web check --skip-instance-checks` gate runs unguarded under
36
+ # `set -e`: a non-zero exit from the CONFIG gate aborts before `exec remo web
37
+ # serve`, so the container fails fast on genuinely bad config/mounts and never
38
+ # starts serving. `--skip-instance-checks` is deliberate: the startup gate
39
+ # validates only config/mounts/executables, NOT per-instance reachability. A
40
+ # registered instance that is merely powered off or unreachable must NOT
41
+ # prevent the whole service from starting (FR-006/US1); under `restart:
42
+ # unless-stopped` (compose.example.yml), gating on reachability would
43
+ # crash-loop the container whenever any single instance is down. Full
44
+ # per-instance diagnostics remain available via `remo web check` (no flag),
45
+ # and readiness is (correctly) config-only via GET /api/v1/ready.
46
+ #
47
+ # 011-web-adopt (SC-006): *unconfigured* is a PASSING state for this gate. A
48
+ # fresh writable state volume with no registry/key material yet makes `remo
49
+ # web check` report "awaiting adoption" and exit 0, so an adopted-mode
50
+ # container boots cleanly (no crash loop) while it waits for adoption. Only
51
+ # genuinely broken configuration still aborts here.
52
+
53
+ set -euo pipefail
54
+
55
+ # Target uid/gid to drop to. Defaults to the image's baked-in `remo` user
56
+ # (1000:1000); override PUID/PGID for a bind-mount host whose directory owner
57
+ # is some other uid/gid.
58
+ PUID="${PUID:-1000}"
59
+ PGID="${PGID:-1000}"
60
+
61
+ # Paths that must be writable by the dropped-to user. CONFIG_DIR mirrors
62
+ # core/config.py's REMO_HOME resolution; CONTROL_DIR mirrors
63
+ # WebSettings.ssh_control_dir (REMO_WEB_SSH_CONTROL_DIR, default /run/remo-ssh).
64
+ CONFIG_DIR="${REMO_HOME:-${HOME:-/home/remo}/.config/remo}"
65
+ CONTROL_DIR="${REMO_WEB_SSH_CONTROL_DIR:-/run/remo-ssh}"
66
+
67
+ if [ "$(id -u)" -eq 0 ]; then
68
+ # Root (the image default): self-heal, then drop. Every step is
69
+ # best-effort (`|| true`) so a read-only mount or a dropped CAP_CHOWN can
70
+ # never abort startup — `remo web check` below is the authority.
71
+ mkdir -p "$CONFIG_DIR" 2>/dev/null || true
72
+ mkdir -p "$CONTROL_DIR" 2>/dev/null || true
73
+ chown -R "$PUID:$PGID" "$CONFIG_DIR" 2>/dev/null || true
74
+ # chmod BEFORE chown so it runs while root still owns the tmpfs (no
75
+ # CAP_FOWNER needed). 1777 (sticky, world-writable) already lets the
76
+ # dropped-to user create control sockets, so the chown that follows is
77
+ # belt-and-suspenders — harmless if it can't change ownership.
78
+ chmod 1777 "$CONTROL_DIR" 2>/dev/null || true
79
+ chown "$PUID:$PGID" "$CONTROL_DIR" 2>/dev/null || true
80
+ # Re-exec this same script as the unprivileged user; the second pass falls
81
+ # through to the gate + serve below.
82
+ exec gosu "$PUID:$PGID" "$0" "$@"
83
+ fi
84
+
85
+ # Unprivileged: either dropped from root above, or started with an explicit
86
+ # non-root `user:` (healing skipped, by design).
87
+ remo web check --skip-instance-checks
88
+ exec remo web serve