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.
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.devcontainer/devcontainer.json +2 -2
- remo_cli-2.2.0rc3/.specify/feature.json +3 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/CLAUDE.md +7 -4
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/PKG-INFO +14 -6
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/README.md +13 -5
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docker/Dockerfile +26 -7
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docker/compose.example.yml +113 -0
- remo_cli-2.2.0rc3/docker/entrypoint.sh +88 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/web-session-interface.md +323 -9
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/api/client.ts +14 -4
- remo_cli-2.2.0rc3/frontend/src/components/AppRoot.tsx +25 -0
- remo_cli-2.2.0rc3/frontend/src/components/AwaitingAdoption.css +115 -0
- remo_cli-2.2.0rc3/frontend/src/components/AwaitingAdoption.tsx +109 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TopBar.tsx +5 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/main.tsx +5 -4
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/health.ts +20 -8
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/pyproject.toml +1 -1
- remo_cli-2.2.0rc3/specs/011-web-adopt/checklists/requirements.md +44 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/contracts/cli-web-adopt.md +78 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/contracts/setup-api.md +98 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/data-model.md +123 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/plan.md +163 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/quickstart-results.md +29 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/quickstart.md +135 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/research.md +311 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/spec.md +507 -0
- remo_cli-2.2.0rc3/specs/011-web-adopt/tasks.md +213 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/web.py +155 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/ssh.py +45 -2
- remo_cli-2.2.0rc3/src/remo_cli/core/web_adopt.py +1338 -0
- remo_cli-2.2.0rc3/src/remo_cli/web/api/setup.py +394 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/app.py +40 -1
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/check.py +71 -4
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/config.py +65 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/discovery.py +9 -1
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/health.py +74 -15
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/logging_config.py +16 -0
- remo_cli-2.2.0rc3/src/remo_cli/web/state.py +287 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/terminal.py +29 -2
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/image/test_docker_image.py +512 -5
- remo_cli-2.2.0rc3/tests/integration/test_web_adopt_e2e.py +736 -0
- remo_cli-2.2.0rc3/tests/unit/cli/test_web_adopt_cmd.py +354 -0
- remo_cli-2.2.0rc3/tests/unit/core/test_ssh_identity_opts.py +363 -0
- remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_authorize.py +346 -0
- remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_payload.py +253 -0
- remo_cli-2.2.0rc3/tests/unit/core/test_web_adopt_trust.py +554 -0
- remo_cli-2.2.0rc3/tests/unit/core/test_web_push.py +630 -0
- remo_cli-2.2.0rc3/tests/unit/web/conftest.py +188 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_csp.py +51 -0
- remo_cli-2.2.0rc3/tests/unit/web/test_health_states.py +304 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_log_redaction.py +39 -0
- remo_cli-2.2.0rc3/tests/unit/web/test_setup_api.py +484 -0
- remo_cli-2.2.0rc3/tests/unit/web/test_setup_auth.py +378 -0
- remo_cli-2.2.0rc3/tests/unit/web/test_state.py +292 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/uv.lock +1 -1
- remo_cli-2.2.0rc1/.specify/feature.json +0 -3
- remo_cli-2.2.0rc1/docker/entrypoint.sh +0 -29
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.dockerignore +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/dependabot.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/release.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.gitignore +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/.registry +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/README.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/extensions.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/init-options.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integration.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/AGENTS.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/CONTRIBUTING.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/Dockerfile +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/LICENSE +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/.ansible-lint +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/README.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/ansible.cfg +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_site.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_site.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/requirements.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/aws.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/hetzner.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/incus.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/install.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/proxmox.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/docs/remo-web.html +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/index.html +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/package-lock.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/package.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/README.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/dist__ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/public/ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/AppShell.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/AppShell.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/SettingsPage.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/ShortcutsModal.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TerminalCard.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TerminalCard.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/WorkspacePane.tsx +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/settings.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/useConsoleKeyboard.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/state/workspace.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/RendererAdapter.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/TerminalConnection.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/XtermRenderer.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/tsconfig.json +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/frontend/vite.config.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/install.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/maverick.yaml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/playwright.config.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/project-menu.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/remo +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/run.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/main.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/models.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/ansible/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/conftest.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/image/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/perf/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/perf/test_latency.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_main.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/test_host_model.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/__init__.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_check.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_discovery.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-2.2.0rc1 → remo_cli-2.2.0rc3}/tests/unit/web/test_terminals_api.py +0 -0
- {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
|
}
|
|
@@ -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-
|
|
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.
|
|
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
|
|
361
|
-
non-root/read-only hardening)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
|
335
|
-
non-root/read-only hardening)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
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
|
-
|
|
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
|