remo-cli 2.2.0rc2__tar.gz → 2.2.0rc4__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.0rc4/.specify/feature.json +3 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/PKG-INFO +1 -1
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docker/Dockerfile +26 -7
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docker/compose.example.yml +70 -29
- remo_cli-2.2.0rc4/docker/entrypoint.sh +88 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/web-session-interface.md +107 -89
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/api/client.ts +75 -0
- remo_cli-2.2.0rc4/frontend/src/components/AwaitingAdoption.tsx +148 -0
- remo_cli-2.2.0rc4/frontend/src/components/PairToSync.css +53 -0
- remo_cli-2.2.0rc4/frontend/src/components/PairToSync.tsx +110 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/TopBar.tsx +3 -0
- remo_cli-2.2.0rc4/frontend/src/lib/clipboard.ts +28 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/pyproject.toml +1 -1
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +63 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/contracts/pairing-api.md +72 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/contracts/setup-api.md +52 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/data-model.md +102 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/plan.md +167 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/quickstart.md +132 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/research.md +287 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/spec.md +435 -0
- remo_cli-2.2.0rc4/specs/012-web-adopt-pairing/tasks.md +239 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/web.py +79 -54
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/web_adopt.py +191 -216
- remo_cli-2.2.0rc4/src/remo_cli/web/api/pairing.py +81 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/api/setup.py +49 -31
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/app.py +39 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/check.py +45 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/config.py +24 -4
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/health.py +23 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/logging_config.py +8 -0
- remo_cli-2.2.0rc4/src/remo_cli/web/operator_auth.py +109 -0
- remo_cli-2.2.0rc4/src/remo_cli/web/pairing.py +142 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/image/test_docker_image.py +301 -27
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_web_adopt_e2e.py +90 -155
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/test_web_adopt_cmd.py +18 -19
- remo_cli-2.2.0rc4/tests/unit/core/test_web_adopt_code.py +56 -0
- remo_cli-2.2.0rc4/tests/unit/core/test_web_push.py +386 -0
- remo_cli-2.2.0rc4/tests/unit/web/_pairing_support.py +53 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_csp.py +6 -4
- remo_cli-2.2.0rc4/tests/unit/web/test_mint_gating.py +57 -0
- remo_cli-2.2.0rc4/tests/unit/web/test_operator_auth.py +90 -0
- remo_cli-2.2.0rc4/tests/unit/web/test_pairing.py +116 -0
- remo_cli-2.2.0rc4/tests/unit/web/test_pairing_origin.py +43 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_setup_api.py +34 -12
- remo_cli-2.2.0rc4/tests/unit/web/test_setup_auth.py +273 -0
- remo_cli-2.2.0rc4/tests/unit/web/test_setup_dormancy.py +110 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_state.py +11 -5
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/uv.lock +1 -1
- remo_cli-2.2.0rc2/.specify/feature.json +0 -3
- remo_cli-2.2.0rc2/docker/entrypoint.sh +0 -37
- remo_cli-2.2.0rc2/frontend/src/components/AwaitingAdoption.tsx +0 -109
- remo_cli-2.2.0rc2/tests/unit/core/test_web_push.py +0 -630
- remo_cli-2.2.0rc2/tests/unit/web/test_setup_auth.py +0 -378
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.dockerignore +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/dependabot.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/release.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.gitignore +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/.registry +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/README.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/extensions.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/init-options.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/integration.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/AGENTS.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/CLAUDE.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/CONTRIBUTING.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/Dockerfile +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/LICENSE +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/README.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/.ansible-lint +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/README.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/ansible.cfg +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/aws_site.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_site.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/requirements.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/aws.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/hetzner.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/incus.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/install.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/proxmox.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/docs/remo-web.html +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/index.html +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/package-lock.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/package.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/public/README.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/public/dist__ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/public/ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/AppRoot.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/AppShell.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/AppShell.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/AwaitingAdoption.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/SettingsPage.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/ShortcutsModal.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/TerminalCard.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/TerminalCard.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/WorkspacePane.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/main.tsx +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/health.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/settings.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/useConsoleKeyboard.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/state/workspace.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/RendererAdapter.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/TerminalConnection.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/XtermRenderer.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/tsconfig.json +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/frontend/vite.config.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/install.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/maverick.yaml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/playwright.config.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/project-menu.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/remo +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/run.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/contracts/setup-api.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/data-model.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/plan.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/quickstart-results.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/quickstart.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/research.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/spec.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/specs/011-web-adopt/tasks.md +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/main.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/ssh.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/discovery.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/models.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/state.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/terminal.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/ansible/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/conftest.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/image/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/perf/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/perf/test_latency.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/test_main.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_ssh_identity_opts.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_web_adopt_authorize.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_web_adopt_payload.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/core/test_web_adopt_trust.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/models/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/test_host_model.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/__init__.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/conftest.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_check.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_discovery.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_health_states.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_log_redaction.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_terminals_api.py +0 -0
- {remo_cli-2.2.0rc2 → remo_cli-2.2.0rc4}/tests/unit/web/test_tokens.py +0 -0
|
@@ -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"]
|
|
@@ -139,10 +139,31 @@ services:
|
|
|
139
139
|
#
|
|
140
140
|
# remo web adopt http://<docker-host>:8080
|
|
141
141
|
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
142
|
+
# Open http://<docker-host>:8080 in a browser (through your forward-auth
|
|
143
|
+
# proxy, see below), click "Copy pairing code", run the command above, and
|
|
144
|
+
# paste the code when prompted. It pushes your registry + instance host
|
|
145
|
+
# keys and authorizes the service's OWN public key on each instance — your
|
|
146
|
+
# personal private key never leaves the workstation. Later registry changes
|
|
147
|
+
# are re-pushed with `remo web push` (open the dashboard's "Pair CLI to
|
|
148
|
+
# sync" affordance for a fresh code).
|
|
149
|
+
#
|
|
150
|
+
# Setup authorization (012-web-adopt-pairing): there is NO static token. The
|
|
151
|
+
# awaiting-adoption page mints a short-lived, single-use PAIRING CODE; the
|
|
152
|
+
# /api/v1/setup/* surface is dormant (404) unless a pairing session is live.
|
|
153
|
+
# Minting a code is gated by OPERATOR AUTHENTICATION:
|
|
154
|
+
#
|
|
155
|
+
# - Recommended: put a forward-auth reverse proxy (Traefik ForwardAuth /
|
|
156
|
+
# oauth2-proxy / Authelia / a hola app's SSO) in front. It must (a) gate
|
|
157
|
+
# the browser-facing POST /api/v1/pairing/mint so only an authenticated
|
|
158
|
+
# operator can mint, while (b) PASSING THROUGH /api/v1/setup/* unchanged
|
|
159
|
+
# (the CLI cannot do SSO — it authenticates with the pairing code alone).
|
|
160
|
+
# Set REMO_WEB_OPERATOR_AUTH=forward and REMO_WEB_FORWARD_AUTH_HEADER to
|
|
161
|
+
# the trusted identity header your proxy injects (e.g. X-Forwarded-User).
|
|
162
|
+
# The proxy MUST strip that header from inbound client requests.
|
|
163
|
+
#
|
|
164
|
+
# - Loopback/dev only: REMO_WEB_OPERATOR_AUTH=none mints WITHOUT operator
|
|
165
|
+
# auth (a loud, weaker posture surfaced in readiness). Use only when the
|
|
166
|
+
# port is loopback-bound or otherwise network-restricted.
|
|
146
167
|
#
|
|
147
168
|
# This service carries `profiles: ["adopted"]` so a plain `docker compose
|
|
148
169
|
# up` still starts only the bind-mount service above. Start this one with:
|
|
@@ -154,25 +175,29 @@ services:
|
|
|
154
175
|
# above — the mode is chosen purely by what is mounted, not by the image.
|
|
155
176
|
image: ghcr.io/get2knowio/remo-web:latest
|
|
156
177
|
ports:
|
|
157
|
-
# Adoption traffic (the
|
|
158
|
-
# `remo web adopt`) arrives over this same port. Loopback-only
|
|
159
|
-
# the workstation running the CLI must be the Docker host itself —
|
|
160
|
-
# otherwise bind a LAN IP here deliberately
|
|
161
|
-
# `remo web adopt --via <docker-host>`
|
|
178
|
+
# Adoption traffic (the pairing-code-authenticated /api/v1/setup/* calls
|
|
179
|
+
# made by `remo web adopt`) arrives over this same port. Loopback-only
|
|
180
|
+
# means the workstation running the CLI must be the Docker host itself —
|
|
181
|
+
# otherwise bind a LAN IP here deliberately (behind your forward-auth
|
|
182
|
+
# proxy), or keep loopback and run `remo web adopt --via <docker-host>`
|
|
183
|
+
# to tunnel over SSH.
|
|
162
184
|
- "127.0.0.1:8080:8080"
|
|
163
185
|
environment:
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
186
|
+
# Operator-authentication posture for pairing-code minting (see the notes
|
|
187
|
+
# above). Choose ONE:
|
|
188
|
+
#
|
|
189
|
+
# Forward auth (recommended, behind an SSO proxy):
|
|
190
|
+
# - "REMO_WEB_OPERATOR_AUTH=forward"
|
|
191
|
+
# - "REMO_WEB_FORWARD_AUTH_HEADER=X-Forwarded-User"
|
|
192
|
+
# (Enabling forward auth without a header name is a fail-fast error.)
|
|
168
193
|
#
|
|
169
|
-
#
|
|
194
|
+
# Network-restricted (loopback/dev only — weaker, loudly logged):
|
|
195
|
+
- "REMO_WEB_OPERATOR_AUTH=none"
|
|
170
196
|
#
|
|
171
|
-
#
|
|
172
|
-
# interactive prompt) when running `remo web adopt`.
|
|
173
|
-
- "REMO_WEB_API_TOKEN=replace-me--generate-with--openssl-rand-hex-24"
|
|
197
|
+
# There is NO REMO_WEB_API_TOKEN anymore — a value set for it is ignored.
|
|
174
198
|
# The REMO_WEB_ALLOWED_HOSTS / REMO_WEB_ALLOWED_ORIGINS notes on the
|
|
175
|
-
# service above apply here unchanged
|
|
199
|
+
# service above apply here unchanged (the mint endpoint needs the page's
|
|
200
|
+
# Origin allowlisted; the default 127.0.0.1:8080 origin covers loopback).
|
|
176
201
|
volumes:
|
|
177
202
|
# A writable named state volume at REMO_HOME — the ONLY volume in this
|
|
178
203
|
# mode (no registry mount, no ~/.ssh mounts; presence of those is what
|
|
@@ -182,23 +207,39 @@ services:
|
|
|
182
207
|
# a NEW identity on next boot; it revokes nothing on the instances —
|
|
183
208
|
# remove the old `remo-web@<deployment-id>` authorized_keys line there.
|
|
184
209
|
- "remo-web-state:/home/remo/.config/remo"
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# *restart* — `docker compose restart`, or the restart policy reviving
|
|
191
|
-
# the process — which would fail the startup runtime-dir check on every
|
|
192
|
-
# boot after the first. `rw,mode=1777` is exactly what Docker mounts on
|
|
193
|
-
# first boot anyway; pinning it just makes restarts behave the same.
|
|
210
|
+
# A PLAIN, option-less tmpfs — no `rw,mode=1777` pin needed anymore. The
|
|
211
|
+
# image starts as root and its entrypoint re-heals ownership on
|
|
212
|
+
# /run/remo-ssh on EVERY start, so the well-known Docker quirk (an
|
|
213
|
+
# option-less tmpfs is remounted root-owned 0755 by a container *restart*)
|
|
214
|
+
# no longer breaks the startup runtime-dir check on the second boot.
|
|
194
215
|
tmpfs:
|
|
195
|
-
- "/run/remo-ssh
|
|
216
|
+
- "/run/remo-ssh"
|
|
196
217
|
read_only: true
|
|
197
|
-
|
|
218
|
+
# NOTE: no `user:` pin here (unlike the bind-mount service above). This
|
|
219
|
+
# service starts as ROOT so the entrypoint can self-heal ownership on the
|
|
220
|
+
# state volume (Docker seeds a fresh named volume root-owned; a bind-mount
|
|
221
|
+
# host dir is root-owned too) before dropping — via gosu — to the
|
|
222
|
+
# unprivileged `remo` user. The app STILL runs as non-root `remo`; only
|
|
223
|
+
# the brief healing pass runs as root. See docker/entrypoint.sh.
|
|
198
224
|
security_opt:
|
|
199
225
|
- "no-new-privileges:true"
|
|
226
|
+
# Drop everything, then add back ONLY the capabilities the heal-then-drop
|
|
227
|
+
# entrypoint needs while it is root: CHOWN (fix ownership of the state
|
|
228
|
+
# dir/tmpfs), DAC_OVERRIDE (traverse/write a dir owned by another uid),
|
|
229
|
+
# FOWNER (defensive — chmod/utime on files owned by another uid),
|
|
230
|
+
# SETUID+SETGID (gosu drops to the `remo` uid).
|
|
231
|
+
# The dropped-to `remo` process holds NONE of these effectively — dropping
|
|
232
|
+
# uid clears the permitted/effective sets — so the security posture is
|
|
233
|
+
# unchanged from a plain non-root run; `no-new-privileges` does not block
|
|
234
|
+
# gosu (dropping privileges is always allowed).
|
|
200
235
|
cap_drop:
|
|
201
236
|
- "ALL"
|
|
237
|
+
cap_add:
|
|
238
|
+
- "CHOWN"
|
|
239
|
+
- "DAC_OVERRIDE"
|
|
240
|
+
- "FOWNER"
|
|
241
|
+
- "SETUID"
|
|
242
|
+
- "SETGID"
|
|
202
243
|
healthcheck:
|
|
203
244
|
# /api/v1/ready answers 200 for BOTH `ready` and `unconfigured` — a
|
|
204
245
|
# container awaiting adoption is healthy and must not crash-loop or
|
|
@@ -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
|
|
@@ -180,30 +180,47 @@ instance — your own LAN, your own tailnet, or behind a reverse proxy you contr
|
|
|
180
180
|
|
|
181
181
|
### Reverse proxies, SSO, and the setup surface
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
183
|
+
> **Breaking change (012-web-adopt-pairing):** the static `REMO_WEB_API_TOKEN` gate is **removed**. A
|
|
184
|
+
> value set for that variable is now ignored. Setup access is authorized by an **ephemeral pairing
|
|
185
|
+
> code** minted from the awaiting-adoption page, not a long-lived secret.
|
|
186
|
+
|
|
187
|
+
The setup API (`/api/v1/setup/*`) used by `remo web adopt`/`remo web push` is **dormant** — every
|
|
188
|
+
route returns `404`, byte-identical to an unknown route — unless a **pairing session** is live. A
|
|
189
|
+
session exists only while an operator is on the awaiting-adoption page (or the dashboard's re-sync
|
|
190
|
+
affordance): opening the page mints a short-lived, single-use pairing code (sliding idle TTL, default
|
|
191
|
+
15 min), the operator copies it to their workstation and pastes it into the CLI, and the code
|
|
192
|
+
authenticates that one adoption/push. See [CLI-to-web adoption](#cli-to-web-adoption).
|
|
193
|
+
|
|
194
|
+
Two properties make this safe:
|
|
195
|
+
|
|
196
|
+
- **Minting a code is gated by operator authentication.** The browser-facing
|
|
197
|
+
`POST /api/v1/pairing/mint` endpoint only mints for an authenticated operator. v1 implements this
|
|
198
|
+
with **forward auth**: put a proxy (Traefik ForwardAuth / oauth2-proxy / Authelia / a hola app's
|
|
199
|
+
SSO) in front that terminates sign-on and injects a trusted identity header, and set
|
|
200
|
+
`REMO_WEB_OPERATOR_AUTH=forward` + `REMO_WEB_FORWARD_AUTH_HEADER=<that header>` (e.g.
|
|
201
|
+
`X-Forwarded-User`). Enabling forward auth without naming a header is a **fail-fast** startup error.
|
|
202
|
+
A loopback/dev deployment may instead set `REMO_WEB_OPERATOR_AUTH=none` (network-restricted — mints
|
|
203
|
+
without operator auth; a loud, weaker posture surfaced in readiness). The check sits behind a
|
|
204
|
+
pluggable provider seam so an in-app OIDC verifier can be added later.
|
|
205
|
+
- **The proxy must split the two paths.** Forward auth applies **only** to `POST /api/v1/pairing/mint`
|
|
206
|
+
— the CLI cannot complete an SSO challenge, so the proxy MUST **pass `/api/v1/setup/*` through**
|
|
207
|
+
unauthenticated at the proxy layer; those routes are authenticated by the pairing code alone. In
|
|
208
|
+
short: gate `/api/v1/pairing/mint` with SSO, pass `/api/v1/setup/*` through.
|
|
209
|
+
|
|
210
|
+
**Forward-auth trust boundary.** The service trusts the identity header only because the deployment
|
|
211
|
+
guarantees the proxy sits in front and **sets/strips** that header — so a client cannot reach the app
|
|
212
|
+
directly and spoof it. This is the standard forward-auth boundary; a deployment that exposes the app
|
|
213
|
+
directly (no proxy) MUST use `REMO_WEB_OPERATOR_AUTH=none` and accept the weaker posture.
|
|
214
|
+
|
|
215
|
+
**Origin-less requests to the setup surface bypass the Origin allowlist — deliberately and safely.**
|
|
216
|
+
The Origin allowlist is a browser-CSRF defense, and the setup API carries no ambient credentials: a
|
|
217
|
+
cross-origin browser request cannot attach an `Authorization` header, and a genuine browser CSRF
|
|
218
|
+
attempt always carries an `Origin` (still enforced). This scoped exemption lets the Origin-less CLI
|
|
219
|
+
reach the setup API, including `--via` tunnels whose `127.0.0.1:<random-port>` origin could never be
|
|
220
|
+
allowlisted. The browser-only `POST /api/v1/pairing/mint` is **not** exempt — it is held to the Origin
|
|
221
|
+
check. See [`setup-api.md`](../specs/012-web-adopt-pairing/contracts/setup-api.md),
|
|
222
|
+
[`pairing-api.md`](../specs/012-web-adopt-pairing/contracts/pairing-api.md), and the middleware in
|
|
223
|
+
`src/remo_cli/web/app.py`.
|
|
207
224
|
|
|
208
225
|
## Deployment modes: mounts vs adoption
|
|
209
226
|
|
|
@@ -217,7 +234,7 @@ var that can drift out of sync with reality):
|
|
|
217
234
|
| SSH identity | **Your personal private key** bind-mounted read-only | A **service-scoped keypair** the container generates itself on first boot (`web-identity/id_ed25519`, comment `remo-web@<deployment-id>`) |
|
|
218
235
|
| Instance host keys | Your `~/.ssh/known_hosts` bind-mounted read-only | Verified host keys pushed by the CLI (`web-identity/known_hosts`) |
|
|
219
236
|
| Volumes | Several read-only bind mounts | **One** writable named volume at `REMO_HOME` (`/home/remo/.config/remo`) — no registry mount, no `~/.ssh` mounts |
|
|
220
|
-
| Required env | — | `
|
|
237
|
+
| Required env | — | `REMO_WEB_OPERATOR_AUTH` (`forward` + `REMO_WEB_FORWARD_AUTH_HEADER`, or `none` for loopback/dev) — gates pairing-code minting; without a provider, minting is disabled and adoption is impossible |
|
|
221
238
|
| Runs where? | Effectively the same machine as your CLI config | Any host — nothing from the workstation is mounted |
|
|
222
239
|
| Registry updates | Edit/sync locally; the mount hot-reloads | `remo web push` after local changes |
|
|
223
240
|
|
|
@@ -235,7 +252,7 @@ From these artifacts the service derives one of four states:
|
|
|
235
252
|
|
|
236
253
|
| State | Derivation | `remo web check` | `GET /api/v1/ready` | Browser |
|
|
237
254
|
|---|---|---|---|---|
|
|
238
|
-
| `unconfigured` | `REMO_HOME` writable, no registry (service keypair may already exist — generated, awaiting first push) | PASS: `unconfigured — awaiting adoption; run 'remo web adopt <service-url>' from a workstation` | **`200`** `{"status": "unconfigured", ...}` — healthy-and-waiting must not fail the compose healthcheck or crash-loop `restart: unless-stopped` | "Awaiting adoption" page: explains the state, shows
|
|
255
|
+
| `unconfigured` | `REMO_HOME` writable, no registry (service keypair may already exist — generated, awaiting first push) | PASS: `unconfigured — awaiting adoption; run 'remo web adopt <service-url>' from a workstation` | **`200`** `{"status": "unconfigured", ...}` — healthy-and-waiting must not fail the compose healthcheck or crash-loop `restart: unless-stopped` | "Awaiting adoption" page: explains the state, shows the `remo web adopt <origin>` command, and a **Copy pairing code** button (the code itself is never displayed). Flips to the dashboard automatically once adoption completes. No instance data, no terminals, no public-key display. |
|
|
239
256
|
| `adopted` | `REMO_HOME` writable + service keypair + registry present | PASS: `adopted — configured via 'remo web adopt' (service identity in web-identity/)` | `200` `{"status": "ready", ...}` | Normal dashboard |
|
|
240
257
|
| `mount_configured` | Registry present **and** (`REMO_HOME` not writable — the `:ro` bind mount — or a user SSH identity resolves via `REMO_WEB_SSH_IDENTITY_FILE`/`~/.ssh/id_*`). Explicit mounts are the operator's stated intent, so this wins even if a service keypair also exists. | PASS: `mount_configured — configured via read-only mounts` | `200` `{"status": "ready", ...}` | Normal dashboard |
|
|
241
258
|
| `broken` | Any required artifact present but unreadable, a half-pair service keypair, a registry on a writable volume with nothing able to authenticate, or a missing runtime prerequisite | FAIL with per-check remediation | `503` `{"status": "not_ready", ...}` with actionable detail — unchanged from today | Offline/error indicator |
|
|
@@ -270,9 +287,9 @@ The file defines **both deployment modes as alternative services** — run one o
|
|
|
270
287
|
|
|
271
288
|
Its differences from `remo-web`: **no bind mounts at all** — a single writable named volume
|
|
272
289
|
(`remo-web-state:/home/remo/.config/remo`) holds the pushed registry, per-instance host keys, and
|
|
273
|
-
the service identity keypair — plus a
|
|
274
|
-
|
|
275
|
-
[CLI-to-web adoption](#cli-to-web-adoption) for what happens after `up`.
|
|
290
|
+
the service identity keypair — plus a `REMO_WEB_OPERATOR_AUTH` setting (`forward` behind an SSO
|
|
291
|
+
proxy, or `none` for loopback/dev) that gates pairing-code minting. Hardening flags are identical.
|
|
292
|
+
See [CLI-to-web adoption](#cli-to-web-adoption) for what happens after `up`.
|
|
276
293
|
|
|
277
294
|
### The published image
|
|
278
295
|
|
|
@@ -358,7 +375,7 @@ tooling.
|
|
|
358
375
|
| Flag | Value | Why |
|
|
359
376
|
|---|---|---|
|
|
360
377
|
| `read_only` | `true` | The image ships no legitimate reason to write to its own filesystem at runtime — all mutable state is either ephemeral (PTYs, SSH ControlMaster sockets) or lives in the explicit `tmpfs` mount. A read-only rootfs means a compromised process can't persist a backdoor into the image layer. |
|
|
361
|
-
| `
|
|
378
|
+
| non-root `remo` (UID 1000) | non-root process | The app always ends up running as the dedicated `remo` user (UID 1000). In the read-only bind-mount service the container is pinned to `user: "1000:1000"`; the adopted-mode service instead starts as **root** and its entrypoint drops to `remo` via `gosu` after self-healing filesystem permissions (chowning a root-owned bind-mounted/named-volume config dir and re-healing the `/run/remo-ssh` tmpfs a restart remounts root-owned). Either way the serving process is non-root, which limits what a container-escape or dependency-confusion bug can do to the host. The adopted service therefore grants back only the minimal capabilities the heal-then-drop needs (`CHOWN`, `DAC_OVERRIDE`, `FOWNER`, `SETUID`, `SETGID`) on top of `cap_drop: ALL`; the dropped-to `remo` process holds none of them effectively. |
|
|
362
379
|
| `security_opt: no-new-privileges:true` | — | Prevents any process in the container (including a compromised one) from gaining privileges via setuid/setgid binaries, closing off a common container-escape vector. |
|
|
363
380
|
| `cap_drop: ALL` | — | The service needs no Linux capabilities at all (it doesn't bind privileged ports, doesn't need `ptrace`, doesn't need raw sockets) — dropping everything removes capabilities an attacker could otherwise abuse. |
|
|
364
381
|
| `restart: unless-stopped` | — | Keeps the service available across host reboots/crashes without fighting an operator's deliberate `docker compose stop`. |
|
|
@@ -377,39 +394,38 @@ Both commands live in the base CLI (`src/remo_cli/cli/web.py` → `src/remo_cli/
|
|
|
377
394
|
stdlib HTTP only) — the `web` extra is **not** required on the workstation:
|
|
378
395
|
|
|
379
396
|
```text
|
|
380
|
-
remo web adopt [URL] [--token TEXT] [--via HOST] [--allow-empty] [--yes]
|
|
381
|
-
remo web push [--allow-empty] [--yes]
|
|
397
|
+
remo web adopt [URL] [--token TEXT] [--via HOST] [--allow-empty] [--yes]
|
|
398
|
+
remo web push [URL] [--token TEXT] [--via HOST] [--allow-empty] [--yes]
|
|
382
399
|
```
|
|
383
400
|
|
|
401
|
+
`--token` carries the **pairing code** (the option name is kept for
|
|
402
|
+
compatibility). Nothing is saved between runs — each adopt/push obtains a fresh
|
|
403
|
+
code from the page.
|
|
404
|
+
|
|
384
405
|
### First-time adoption walkthrough
|
|
385
406
|
|
|
386
407
|
**1. Deploy the container.** Via Compose (`docker compose --profile adopted up -d`, see
|
|
387
|
-
[Docker Compose deployment](#docker-compose-deployment)) or as an **hola app** —
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
run.
|
|
408
|
+
[Docker Compose deployment](#docker-compose-deployment)) or as an **hola app** — set
|
|
409
|
+
`REMO_WEB_OPERATOR_AUTH` (`forward` behind the hola app's SSO, plus `REMO_WEB_FORWARD_AUTH_HEADER`;
|
|
410
|
+
or `none` for a loopback/dev deployment) so the page can mint pairing codes. Within ~30 seconds the
|
|
411
|
+
container is up in the `unconfigured` state, has minted its service-scoped keypair, and the browser
|
|
412
|
+
shows the "awaiting adoption" page.
|
|
393
413
|
|
|
394
|
-
**2.
|
|
414
|
+
**2. Copy the pairing code and run `remo web adopt`.** On the awaiting-adoption page (reached through
|
|
415
|
+
your SSO proxy), click **Copy pairing code** — the code lands on your clipboard and is never
|
|
416
|
+
displayed. On the workstation, inputs resolve in this order:
|
|
395
417
|
|
|
396
418
|
| Input | Resolution order |
|
|
397
419
|
|---|---|
|
|
398
420
|
| Service URL | argument → `REMO_API_URL` env var → interactive prompt |
|
|
399
|
-
|
|
|
400
|
-
|
|
401
|
-
`REMO_API_URL`/`REMO_API_TOKEN` are the workstation-side counterparts of the service's
|
|
402
|
-
`REMO_WEB_API_TOKEN` — set them (e.g. exported by your shell profile, or handed out by the hola app
|
|
403
|
-
install output) and the command runs prompt-free:
|
|
421
|
+
| Pairing code | `--token` → `REMO_API_TOKEN` env var → interactive prompt (hidden input) |
|
|
404
422
|
|
|
405
423
|
```bash
|
|
406
|
-
|
|
407
|
-
export REMO_API_TOKEN=<the value you set as REMO_WEB_API_TOKEN>
|
|
408
|
-
remo web adopt
|
|
424
|
+
remo web adopt http://docker-host.lan:8080 # paste the code at the prompt
|
|
409
425
|
```
|
|
410
426
|
|
|
411
427
|
The flow then: checks the service's state (aborting clearly if the target is mount-configured or the
|
|
412
|
-
|
|
428
|
+
code is no longer valid), fetches the service's public key and deployment id, and — per direct-access
|
|
413
429
|
instance, with a bounded per-instance time budget so one slow instance delays only itself —
|
|
414
430
|
`ssh-keyscan`s the host, verifies the scanned key against your own trusted `~/.ssh/known_hosts`
|
|
415
431
|
record (`ssh-keygen -F`, so hashed known_hosts files work; the service itself **never** makes a
|
|
@@ -437,52 +453,50 @@ asymmetric-network case (e.g. the instance is only reachable via workstation-spe
|
|
|
437
453
|
config such as ProxyJump, or a firewall between the container host and the instance), not an
|
|
438
454
|
adoption failure.
|
|
439
455
|
|
|
440
|
-
**5.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
456
|
+
**5. No saved credentials.** Nothing durable is saved (there is no long-lived secret to save). A later
|
|
457
|
+
`remo web push` obtains a fresh pairing code the same way. The workstation keeps only a **non-secret**
|
|
458
|
+
push cache at `~/.config/remo/web-service.json` (mode `0600`): the service `deployment_id` mapped to
|
|
459
|
+
per-instance host-key fingerprints, used to skip re-keyscanning unchanged instances. No URL and no
|
|
460
|
+
code are ever stored.
|
|
445
461
|
|
|
446
462
|
The command exits `0` when the flow completes — per-instance skips/flags are reported in the
|
|
447
|
-
summary, not fatal — and `1` only on hard failure (
|
|
448
|
-
registry without `--allow-empty`, tunnel failure, payload rejected).
|
|
449
|
-
same summary, zero changes, still exactly one
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
`
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
- **Wrong/missing
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
- **
|
|
466
|
-
|
|
467
|
-
|
|
463
|
+
summary, not fatal — and `1` only on hard failure (dormant setup surface / expired code,
|
|
464
|
+
mount-configured target, empty registry without `--allow-empty`, tunnel failure, payload rejected).
|
|
465
|
+
Re-running adopt (with a fresh code) is idempotent: same summary, zero changes, still exactly one
|
|
466
|
+
`remo-web@` line per instance.
|
|
467
|
+
|
|
468
|
+
### The setup API and pairing codes
|
|
469
|
+
|
|
470
|
+
The CLI talks to four endpoints under `/api/v1/setup/*`
|
|
471
|
+
([`setup-api.md`](../specs/012-web-adopt-pairing/contracts/setup-api.md)): `GET /status`,
|
|
472
|
+
`GET /identity`, `PUT /registry`, `POST /verify`. The surface is **dormant** unless a pairing session
|
|
473
|
+
is live; each route requires `Authorization: Bearer <pairing-code>`, compared in constant time:
|
|
474
|
+
|
|
475
|
+
- **No live session → the setup surface does not exist.** Every `/api/v1/setup/*` request gets a plain
|
|
476
|
+
`404`, indistinguishable from an absent feature — fail closed. A session is live only while an
|
|
477
|
+
operator is on the awaiting-adoption page (or the dashboard re-sync affordance).
|
|
478
|
+
- **Wrong/missing/expired code → the same dormant `404`** — never a distinguishable `401` that would
|
|
479
|
+
reveal a session exists. The attempt is logged without the presented code; codes and `Authorization`
|
|
480
|
+
headers are covered by the service's log redaction (`src/remo_cli/web/logging_config.py`).
|
|
481
|
+
- **The session ends when the flow completes** (on the terminal `POST /verify`), and a code is
|
|
482
|
+
single-use per handoff — reopening the page mints a fresh one and invalidates the prior. There is no
|
|
483
|
+
rotation to manage: codes are ephemeral by construction.
|
|
468
484
|
|
|
469
485
|
### Ongoing pushes: `remo web push`
|
|
470
486
|
|
|
471
487
|
After the initial adoption, local registry changes (a `remo <provider> sync`, a new `create`, a
|
|
472
|
-
removal) are re-synced
|
|
488
|
+
removal) are re-synced. Open the dashboard's **Pair CLI to sync** affordance, copy a fresh code, then:
|
|
473
489
|
|
|
474
490
|
```bash
|
|
475
|
-
remo incus sync my-incus-host
|
|
476
|
-
remo web push
|
|
491
|
+
remo incus sync my-incus-host # e.g. registers a new instance locally
|
|
492
|
+
remo web push http://docker-host.lan:8080 # paste the code; re-sync to the service
|
|
477
493
|
```
|
|
478
494
|
|
|
479
|
-
`remo web push`
|
|
480
|
-
`deployment_id
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
replaces the service's host-keys file wholesale). If no credentials were saved, `push` behaves
|
|
485
|
-
exactly like first-time `adopt`, prompting for URL and token.
|
|
495
|
+
`remo web push` resolves URL + code the same way `adopt` does (every run), reads the service's
|
|
496
|
+
`deployment_id`, and runs the adopt flow with **delta behavior**: only new or changed instances are
|
|
497
|
+
re-keyscanned and re-authorized; instances unchanged since the last successful push (per the non-secret
|
|
498
|
+
push cache for that `deployment_id`) skip that work and are reported as `unchanged` (their previously
|
|
499
|
+
verified host keys are reused, since every push replaces the service's host-keys file wholesale).
|
|
486
500
|
|
|
487
501
|
**Mirror semantics — removals propagate, authorization does not.** The push is an exact mirror: the
|
|
488
502
|
workstation registry is the source of truth, so an instance you removed locally disappears from the
|
|
@@ -623,12 +637,13 @@ interactive session (only `remo-host capabilities` is invoked, never `sessions a
|
|
|
623
637
|
|
|
624
638
|
| Failure | What it means | Fix |
|
|
625
639
|
|---|---|---|
|
|
626
|
-
| `/api/v1/setup/*` returns `404` for everything |
|
|
627
|
-
| `remo web adopt` fails
|
|
640
|
+
| `/api/v1/setup/*` returns `404` for everything | No pairing session is live — the surface is dormant (fail closed). | Open the awaiting-adoption page (through your SSO proxy) to mint a code; if the page can't mint, set `REMO_WEB_OPERATOR_AUTH` (`forward` + header, or `none` for loopback). |
|
|
641
|
+
| `remo web adopt`/`push` fails: "pairing code is no longer valid … dormant" | The code expired (idle TTL), was rotated by reopening the page, or was already used. | Reopen the adopt page (or the dashboard's "Pair CLI to sync" affordance) for a fresh code and retry. |
|
|
642
|
+
| Mint page shows "you are not signed in" / `POST /pairing/mint` returns `403` | Forward auth is required but the request reached the service without the trusted identity header. | Ensure the request goes through the SSO proxy that injects `REMO_WEB_FORWARD_AUTH_HEADER`; verify the proxy sets and strips it. |
|
|
628
643
|
| adopt fails: deployment "configured via read-only mounts" | The target is a bind-mount deployment (`mount_configured`) — its configuration is operator-provided and read-only, so adoption does not apply. | Update the mounted files instead, or deploy the adopted-mode service (writable state volume, no mounts) if you want adoption. |
|
|
629
644
|
| adopt refuses: empty registry | Your local registry has no instances — pushing would wipe a previously adopted service (a classic wrong-workstation accident). | Register/sync instances first, or pass `--allow-empty` if wiping is intentional. |
|
|
630
645
|
| `--via` fails naming `REMO_WEB_ALLOWED_HOSTS` | Tunneled requests arrive with a `127.0.0.1` Host header, which the service's Host allowlist rejects. | Add `127.0.0.1` to `REMO_WEB_ALLOWED_HOSTS` (the default includes it). |
|
|
631
|
-
|
|
|
646
|
+
| After a service state-volume reset, instances keep a stale `remo-web@` line | The reset service minted a new identity; a fresh `remo web adopt` authorizes the new key but does not remove the old line. | Re-run `remo web adopt`, then delete the stale `remo-web@<old-id>` line from each instance's `~/.ssh/authorized_keys`. |
|
|
632
647
|
| Summary line `security_flagged` (potential MITM warning) | The instance's scanned host key doesn't match your workstation's trusted record; nothing was pushed for it. | Investigate before trusting. If the instance was legitimately rebuilt: `ssh-keygen -R <host>`, reconnect once to re-trust, re-run adopt. |
|
|
633
648
|
| Verify report: "reachable from workstation but not from the service" | Asymmetric reachability — the CLI reached the instance but the container cannot (DNS, routing, firewall, or workstation-only SSH config like ProxyJump). | Fix the network path from the container host to the instance; the adoption itself succeeded. |
|
|
634
649
|
|
|
@@ -688,7 +703,10 @@ locally with zero configuration; a container overrides everything via env alone.
|
|
|
688
703
|
| `REMO_WEB_SSH_CONTROL_DIR` | `/run/remo-ssh` | Writable directory for SSH ControlMaster sockets (must be tmpfs or otherwise writable under a read-only rootfs). |
|
|
689
704
|
| `REMO_WEB_FRONTEND_DIST_DIR` | `<repo_root>/frontend/dist` (resolved relative to the installed package) | Directory the built frontend SPA is served from. The Docker image overrides this to `/app/frontend-dist`, matching where the multi-stage build actually copies the built assets. |
|
|
690
705
|
| `REMO_WEB_SSH_IDENTITY_FILE` | *(unset — falls back to the service keypair under `web-identity/`, then `~/.ssh/id_ed25519`/`id_ecdsa`/`id_rsa`/`id_dsa`)* | Explicit path to the SSH private key used for readiness/`remo web check`'s identity check, when it isn't one of the conventional filenames. |
|
|
691
|
-
| `
|
|
706
|
+
| `REMO_WEB_OPERATOR_AUTH` | *(unset — minting disabled)* | Operator-authentication posture gating pairing-code minting (`POST /api/v1/pairing/mint`). `forward` requires a trusted proxy-injected identity header (`REMO_WEB_FORWARD_AUTH_HEADER`); `none` mints without operator auth (network-restricted — a loud, weaker posture for loopback/dev). While unset, minting is disabled and adoption is impossible (fail closed). |
|
|
707
|
+
| `REMO_WEB_FORWARD_AUTH_HEADER` | *(unset)* | Name of the trusted identity header your forward-auth proxy injects (e.g. `X-Forwarded-User`, `Remote-User`). **Required** when `REMO_WEB_OPERATOR_AUTH=forward`; enabling forward auth without it is a fail-fast startup error. The proxy MUST set and strip this header. |
|
|
708
|
+
| `REMO_WEB_PAIRING_TTL_S` | `900.0` | Sliding idle TTL (seconds) for a pairing session — it expires this long after the last successful setup call (default 15 min). |
|
|
709
|
+
| `REMO_WEB_API_TOKEN` | *(removed — ignored)* | **Removed in 012.** The static setup-API token is gone; a value set here is ignored (a one-line "now ignored" note is logged at startup). Setup access is authorized by ephemeral pairing codes. |
|
|
692
710
|
|
|
693
711
|
`remo web serve --host`/`--port` are convenience overrides for local runs; every other setting is env-var-only.
|
|
694
712
|
|
|
@@ -700,4 +718,4 @@ Two variables configure the **CLI** (not the service — hence no `REMO_WEB_` pr
|
|
|
700
718
|
| Variable | Used as |
|
|
701
719
|
|---|---|
|
|
702
720
|
| `REMO_API_URL` | Service URL fallback when no URL argument is given (before falling back to an interactive prompt). |
|
|
703
|
-
| `REMO_API_TOKEN` |
|
|
721
|
+
| `REMO_API_TOKEN` | Pairing-code fallback when `--token` is not given (before falling back to a hidden interactive prompt). Set it to a code freshly minted from the adopt page. |
|