remo-cli 1.0.2__tar.gz → 2.0.0__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-1.0.2 → remo_cli-2.0.0}/.devcontainer/devcontainer.json +4 -2
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/smoke-test.yml +220 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.gitignore +3 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/CLAUDE.md +2 -1
- {remo_cli-1.0.2 → remo_cli-2.0.0}/PKG-INFO +77 -1
- {remo_cli-1.0.2 → remo_cli-2.0.0}/README.md +76 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/user_setup/tasks/main.yml +26 -2
- remo_cli-2.0.0/ansible/roles/user_setup/templates/project-launch.sh.j2 +147 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/docs/aws.md +110 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/pyproject.toml +1 -1
- remo_cli-2.0.0/specs/005-provider-snapshots/checklists/requirements.md +36 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/contracts/cli-surface.md +160 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/data-model.md +114 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/plan.md +101 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/quickstart.md +161 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/research.md +132 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/spec.md +181 -0
- remo_cli-2.0.0/specs/005-provider-snapshots/tasks.md +286 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/main.py +18 -1
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/providers/aws.py +125 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/providers/hetzner.py +105 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/providers/incus.py +140 -2
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/providers/proxmox.py +181 -2
- remo_cli-2.0.0/src/remo_cli/cli/shell.py +196 -0
- remo_cli-2.0.0/src/remo_cli/core/snapshot.py +170 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/ssh.py +92 -15
- remo_cli-2.0.0/src/remo_cli/models/snapshot.py +43 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/providers/aws.py +505 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/providers/hetzner.py +335 -1
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/providers/incus.py +363 -3
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/providers/proxmox.py +366 -6
- remo_cli-2.0.0/tests/unit/cli/providers/test_aws_snapshot.py +126 -0
- remo_cli-2.0.0/tests/unit/cli/providers/test_hetzner_snapshot.py +129 -0
- remo_cli-2.0.0/tests/unit/cli/providers/test_incus_snapshot.py +212 -0
- remo_cli-2.0.0/tests/unit/cli/providers/test_proxmox_snapshot.py +158 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/cli/test_main.py +39 -0
- remo_cli-2.0.0/tests/unit/cli/test_shell.py +403 -0
- remo_cli-2.0.0/tests/unit/core/test_snapshot.py +176 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_ssh.py +44 -18
- remo_cli-2.0.0/tests/unit/providers/test_aws_snapshot.py +458 -0
- remo_cli-2.0.0/tests/unit/providers/test_hetzner_snapshot.py +372 -0
- remo_cli-2.0.0/tests/unit/providers/test_incus_snapshot.py +467 -0
- remo_cli-2.0.0/tests/unit/providers/test_proxmox_snapshot.py +465 -0
- remo_cli-2.0.0/tests/unit/test_ansible_templates.py +36 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/uv.lock +1 -1
- remo_cli-1.0.2/src/remo_cli/cli/shell.py +0 -108
- remo_cli-1.0.2/tests/unit/cli/test_shell.py +0 -178
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.analyze.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.checklist.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.clarify.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.constitution.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.implement.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.plan.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.specify.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.tasks.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.claude/commands/speckit.taskstoissues.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.dockerignore +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/dependabot.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/ci.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/provision.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/release.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.github/workflows/teardown.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/memory/constitution.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/templates/plan-template.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/templates/spec-template.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/CONTRIBUTING.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/Dockerfile +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/LICENSE +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/.ansible-lint +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/README.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/ansible.cfg +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/aws_configure.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/aws_resize.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/aws_site.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/aws_teardown.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/group_vars/all.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/hetzner_site.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_configure.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_provision.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_resize.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_site.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/incus_teardown.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_site.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/requirements.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/docs/hetzner.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/docs/incus.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/docs/install.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/docs/proxmox.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/install.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/maverick.yaml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/project-menu.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/remo +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/run.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/__main__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/config.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/output.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/core/version.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/models/host.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/conftest.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/integration/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/integration/orbstack.sh +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_config.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_output.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/core/test_version.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-1.0.2 → remo_cli-2.0.0}/tests/unit/test_host_model.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Python Agentic Development",
|
|
3
|
-
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
|
|
4
4
|
"remoteUser": "vscode",
|
|
5
5
|
"features": {
|
|
6
6
|
"ghcr.io/devcontainers/features/python:1": {},
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
"source=${localEnv:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,consistency=cached",
|
|
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
|
-
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
|
|
43
|
+
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached",
|
|
44
|
+
"source=${localWorkspaceFolder}/../site,target=/workspaces/site,type=bind,consistency=cached",
|
|
45
|
+
"source=${localWorkspaceFolder}/../remo-broker,target=/workspaces/remo-broker,type=bind,consistency=cached"
|
|
44
46
|
],
|
|
45
47
|
"postCreateCommand": "python3 --version && node --version"
|
|
46
48
|
}
|
|
@@ -139,9 +139,55 @@ jobs:
|
|
|
139
139
|
echo "$OUT" | grep -q "EBS volume:" || { echo "FAIL: missing EBS volume"; exit 1; }
|
|
140
140
|
echo "aws info: PASS"
|
|
141
141
|
|
|
142
|
+
- name: Snapshot — create (async kickoff)
|
|
143
|
+
run: |
|
|
144
|
+
SNAP=ci-smoke-aws
|
|
145
|
+
echo "SNAP_NAME=$SNAP" >> "$GITHUB_ENV"
|
|
146
|
+
OUT=$(uv run remo aws snapshot create "$INSTANCE_NAME" --name "$SNAP" --description "smoke test")
|
|
147
|
+
echo "$OUT"
|
|
148
|
+
echo "$OUT" | grep -q "creation started" || { echo "FAIL: missing async-create hint"; exit 1; }
|
|
149
|
+
echo "$OUT" | grep -q "several minutes" || { echo "FAIL: missing duration hint"; exit 1; }
|
|
150
|
+
echo "aws snapshot create: PASS"
|
|
151
|
+
|
|
152
|
+
- name: Snapshot — list shows pending then available
|
|
153
|
+
run: |
|
|
154
|
+
# Initial list: pending (or already available if AWS is fast today)
|
|
155
|
+
uv run remo aws snapshot list "$INSTANCE_NAME" | tee /tmp/list-1.txt
|
|
156
|
+
grep -q "$SNAP_NAME" /tmp/list-1.txt || { echo "FAIL: snapshot missing from initial list"; exit 1; }
|
|
157
|
+
grep -q "STATUS" /tmp/list-1.txt || { echo "FAIL: AWS list missing STATUS column"; exit 1; }
|
|
158
|
+
|
|
159
|
+
# Poll up to 10 minutes for the snapshot to reach available state
|
|
160
|
+
for attempt in $(seq 1 60); do
|
|
161
|
+
OUT=$(uv run remo aws snapshot list "$INSTANCE_NAME")
|
|
162
|
+
if echo "$OUT" | grep -E "^\s*$INSTANCE_NAME\s+$SNAP_NAME\s.*\savailable" >/dev/null; then
|
|
163
|
+
echo "Snapshot $SNAP_NAME is available (attempt $attempt)"
|
|
164
|
+
break
|
|
165
|
+
fi
|
|
166
|
+
echo "Attempt $attempt: not yet available; sleeping 10s..."
|
|
167
|
+
sleep 10
|
|
168
|
+
done
|
|
169
|
+
uv run remo aws snapshot list "$INSTANCE_NAME" | grep -E "^\s*$INSTANCE_NAME\s+$SNAP_NAME\s.*\savailable" \
|
|
170
|
+
|| { echo "FAIL: snapshot did not reach available within 10 min"; exit 1; }
|
|
171
|
+
echo "aws snapshot list: PASS"
|
|
172
|
+
|
|
173
|
+
- name: Snapshot — delete
|
|
174
|
+
run: |
|
|
175
|
+
uv run remo aws snapshot delete "$INSTANCE_NAME" "$SNAP_NAME" --yes
|
|
176
|
+
# Verify it's gone from list
|
|
177
|
+
OUT=$(uv run remo aws snapshot list "$INSTANCE_NAME")
|
|
178
|
+
if echo "$OUT" | grep -q "$SNAP_NAME"; then
|
|
179
|
+
echo "FAIL: snapshot still present after delete"; exit 1
|
|
180
|
+
fi
|
|
181
|
+
echo "aws snapshot delete: PASS"
|
|
182
|
+
|
|
142
183
|
- name: Teardown
|
|
143
184
|
if: always()
|
|
144
185
|
run: |
|
|
186
|
+
# Best-effort cleanup of any leftover smoke snapshots (in case of
|
|
187
|
+
# mid-step failure) before tearing down the instance.
|
|
188
|
+
if [ -n "${SNAP_NAME:-}" ]; then
|
|
189
|
+
uv run remo aws snapshot delete "$INSTANCE_NAME" "$SNAP_NAME" --yes 2>/dev/null || true
|
|
190
|
+
fi
|
|
145
191
|
uv run remo aws destroy --name "$INSTANCE_NAME" --yes --remove-storage || true
|
|
146
192
|
|
|
147
193
|
# ===========================================================================
|
|
@@ -217,9 +263,51 @@ jobs:
|
|
|
217
263
|
echo "$OUT" | grep -q "Volume:" || { echo "FAIL: missing Volume"; exit 1; }
|
|
218
264
|
echo "hetzner info: PASS"
|
|
219
265
|
|
|
266
|
+
- name: Snapshot — create (async kickoff)
|
|
267
|
+
run: |
|
|
268
|
+
SNAP=ci-smoke-hetzner
|
|
269
|
+
echo "SNAP_NAME=$SNAP" >> "$GITHUB_ENV"
|
|
270
|
+
OUT=$(uv run remo hetzner snapshot create "$INSTANCE_NAME" --name "$SNAP" --description "smoke test")
|
|
271
|
+
echo "$OUT"
|
|
272
|
+
echo "$OUT" | grep -q "creation started" || { echo "FAIL: missing async-create hint"; exit 1; }
|
|
273
|
+
echo "$OUT" | grep -q "several minutes" || { echo "FAIL: missing duration hint"; exit 1; }
|
|
274
|
+
echo "hetzner snapshot create: PASS"
|
|
275
|
+
|
|
276
|
+
- name: Snapshot — list shows pending then available
|
|
277
|
+
run: |
|
|
278
|
+
uv run remo hetzner snapshot list "$INSTANCE_NAME" | tee /tmp/list-1.txt
|
|
279
|
+
grep -q "$SNAP_NAME" /tmp/list-1.txt || { echo "FAIL: snapshot missing from initial list"; exit 1; }
|
|
280
|
+
grep -q "STATUS" /tmp/list-1.txt || { echo "FAIL: Hetzner list missing STATUS column"; exit 1; }
|
|
281
|
+
|
|
282
|
+
# Poll up to 5 minutes for the snapshot to become available
|
|
283
|
+
for attempt in $(seq 1 30); do
|
|
284
|
+
OUT=$(uv run remo hetzner snapshot list "$INSTANCE_NAME")
|
|
285
|
+
if echo "$OUT" | grep -E "^\s*$INSTANCE_NAME\s+$SNAP_NAME\s.*\savailable" >/dev/null; then
|
|
286
|
+
echo "Snapshot $SNAP_NAME is available (attempt $attempt)"
|
|
287
|
+
break
|
|
288
|
+
fi
|
|
289
|
+
echo "Attempt $attempt: not yet available; sleeping 10s..."
|
|
290
|
+
sleep 10
|
|
291
|
+
done
|
|
292
|
+
uv run remo hetzner snapshot list "$INSTANCE_NAME" | grep -E "^\s*$INSTANCE_NAME\s+$SNAP_NAME\s.*\savailable" \
|
|
293
|
+
|| { echo "FAIL: snapshot did not reach available within 5 min"; exit 1; }
|
|
294
|
+
echo "hetzner snapshot list: PASS"
|
|
295
|
+
|
|
296
|
+
- name: Snapshot — delete
|
|
297
|
+
run: |
|
|
298
|
+
uv run remo hetzner snapshot delete "$INSTANCE_NAME" "$SNAP_NAME" --yes
|
|
299
|
+
OUT=$(uv run remo hetzner snapshot list "$INSTANCE_NAME")
|
|
300
|
+
if echo "$OUT" | grep -q "$SNAP_NAME"; then
|
|
301
|
+
echo "FAIL: snapshot still present after delete"; exit 1
|
|
302
|
+
fi
|
|
303
|
+
echo "hetzner snapshot delete: PASS"
|
|
304
|
+
|
|
220
305
|
- name: Teardown
|
|
221
306
|
if: always()
|
|
222
307
|
run: |
|
|
308
|
+
if [ -n "${SNAP_NAME:-}" ]; then
|
|
309
|
+
uv run remo hetzner snapshot delete "$INSTANCE_NAME" "$SNAP_NAME" --yes 2>/dev/null || true
|
|
310
|
+
fi
|
|
223
311
|
uv run remo hetzner destroy --name "$INSTANCE_NAME" --yes --remove-volume || true
|
|
224
312
|
|
|
225
313
|
# ===========================================================================
|
|
@@ -365,6 +453,93 @@ jobs:
|
|
|
365
453
|
verify_tools
|
|
366
454
|
echo "Post-update dev tools verification: PASS"
|
|
367
455
|
|
|
456
|
+
- name: Snapshot — create
|
|
457
|
+
run: |
|
|
458
|
+
SNAP=ci-smoke-incus
|
|
459
|
+
echo "SNAP_NAME=$SNAP" >> "$GITHUB_ENV"
|
|
460
|
+
OUT=$(sg incus-admin -c "uv run remo incus snapshot create $CONTAINER_NAME --name $SNAP --description 'smoke test'")
|
|
461
|
+
echo "$OUT"
|
|
462
|
+
echo "$OUT" | grep -q "Created snapshot '$SNAP'" || { echo "FAIL: missing sync-create success message"; exit 1; }
|
|
463
|
+
echo "incus snapshot create: PASS"
|
|
464
|
+
|
|
465
|
+
- name: Snapshot — list shows the new snapshot
|
|
466
|
+
run: |
|
|
467
|
+
OUT=$(sg incus-admin -c "uv run remo incus snapshot list $CONTAINER_NAME")
|
|
468
|
+
echo "$OUT"
|
|
469
|
+
echo "$OUT" | grep -q "$SNAP_NAME" || { echo "FAIL: snapshot missing from list"; exit 1; }
|
|
470
|
+
# Incus list MUST NOT have a STATUS column (always AVAILABLE)
|
|
471
|
+
if echo "$OUT" | head -1 | grep -q "STATUS"; then
|
|
472
|
+
echo "FAIL: Incus list should not have a STATUS column"; exit 1
|
|
473
|
+
fi
|
|
474
|
+
echo "incus snapshot list: PASS"
|
|
475
|
+
|
|
476
|
+
- name: Snapshot — restore round-trip
|
|
477
|
+
run: |
|
|
478
|
+
SSH_CMD="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
479
|
+
-o ConnectTimeout=30 -o BatchMode=yes -i ~/.ssh/id_rsa remo@$HOST"
|
|
480
|
+
|
|
481
|
+
# Plant a marker file inside the container AFTER the snapshot was
|
|
482
|
+
# taken. If restore works, this marker must be gone afterwards.
|
|
483
|
+
$SSH_CMD "echo post-snapshot > /tmp/should-be-gone-after-restore.txt"
|
|
484
|
+
$SSH_CMD "test -f /tmp/should-be-gone-after-restore.txt"
|
|
485
|
+
echo "Marker file planted post-snapshot."
|
|
486
|
+
|
|
487
|
+
# Restore. -y to bypass the confirm. The provider stops the
|
|
488
|
+
# container, restores, and starts it again.
|
|
489
|
+
sg incus-admin -c "uv run remo incus snapshot restore $CONTAINER_NAME $SNAP_NAME -y"
|
|
490
|
+
|
|
491
|
+
# Container may take a few seconds to come back online after start.
|
|
492
|
+
for attempt in $(seq 1 12); do
|
|
493
|
+
if $SSH_CMD "echo connection-ok" 2>/dev/null | grep -q "connection-ok"; then
|
|
494
|
+
echo "SSH back after restore (attempt $attempt)"
|
|
495
|
+
break
|
|
496
|
+
fi
|
|
497
|
+
sleep 5
|
|
498
|
+
done
|
|
499
|
+
|
|
500
|
+
# The post-snapshot marker MUST be gone (SC-001).
|
|
501
|
+
if $SSH_CMD "test -f /tmp/should-be-gone-after-restore.txt" 2>/dev/null; then
|
|
502
|
+
echo "FAIL: marker file still present after restore — rollback didn't happen"
|
|
503
|
+
exit 1
|
|
504
|
+
fi
|
|
505
|
+
echo "Post-restore: marker file is gone — rollback PASS"
|
|
506
|
+
echo "incus snapshot restore: PASS"
|
|
507
|
+
|
|
508
|
+
- name: Snapshot — delete
|
|
509
|
+
run: |
|
|
510
|
+
sg incus-admin -c "uv run remo incus snapshot delete $CONTAINER_NAME $SNAP_NAME -y"
|
|
511
|
+
OUT=$(sg incus-admin -c "uv run remo incus snapshot list $CONTAINER_NAME")
|
|
512
|
+
if echo "$OUT" | grep -q "$SNAP_NAME"; then
|
|
513
|
+
echo "FAIL: snapshot still present after delete"
|
|
514
|
+
echo "$OUT"
|
|
515
|
+
exit 1
|
|
516
|
+
fi
|
|
517
|
+
echo "incus snapshot delete: PASS"
|
|
518
|
+
|
|
519
|
+
- name: Snapshot — destroy-time cleanup prompt
|
|
520
|
+
run: |
|
|
521
|
+
# Create two snapshots, then destroy with -y. The destroy-time prompt
|
|
522
|
+
# is bypassed by -y, defaulting to KEEP snapshots (safer default).
|
|
523
|
+
# The destroy output must mention the snapshots and the keep-warning.
|
|
524
|
+
# We re-create the container after so the rest of the suite (cp tests)
|
|
525
|
+
# has a clean target.
|
|
526
|
+
sg incus-admin -c "uv run remo incus snapshot create $CONTAINER_NAME --name keep-1"
|
|
527
|
+
sg incus-admin -c "uv run remo incus snapshot create $CONTAINER_NAME --name keep-2"
|
|
528
|
+
|
|
529
|
+
# Capture destroy output to verify the prompt path fires
|
|
530
|
+
OUT=$(sg incus-admin -c "uv run remo incus destroy --name $CONTAINER_NAME --yes" 2>&1 || true)
|
|
531
|
+
echo "$OUT"
|
|
532
|
+
echo "$OUT" | grep -q "has 2 snapshot" || { echo "FAIL: destroy did not mention snapshots"; exit 1; }
|
|
533
|
+
echo "$OUT" | grep -q "keeping the 2 snapshot" || { echo "FAIL: destroy did not print --yes keep warning"; exit 1; }
|
|
534
|
+
echo "incus destroy snapshot-prompt: PASS"
|
|
535
|
+
|
|
536
|
+
# Re-create the container for downstream steps (cp tests etc.)
|
|
537
|
+
sg incus-admin -c "uv run remo incus create --name $CONTAINER_NAME"
|
|
538
|
+
# Refresh $HOST since the container was rebuilt
|
|
539
|
+
HOST=$(sudo incus list $CONTAINER_NAME --format=json | \
|
|
540
|
+
python3 -c "import sys,json; d=json.load(sys.stdin)[0]['state']['network']['eth0']['addresses']; print([a['address'] for a in d if a['family']=='inet'][0])")
|
|
541
|
+
echo "HOST=$HOST" >> "$GITHUB_ENV"
|
|
542
|
+
|
|
368
543
|
- name: Prepare for remo cp tests
|
|
369
544
|
run: |
|
|
370
545
|
# Make container name resolvable (remo stores name, not IP, for Incus)
|
|
@@ -628,6 +803,51 @@ jobs:
|
|
|
628
803
|
$SSH_CMD "pkill -f 'python3 -m http.server 9090'" 2>/dev/null || true
|
|
629
804
|
$SSH_CMD "pkill -f 'python3 -m http.server 3000'" 2>/dev/null || true
|
|
630
805
|
|
|
806
|
+
- name: Test remo shell -p / --exec / --detach (project-launch passthrough)
|
|
807
|
+
run: |
|
|
808
|
+
SSH_CMD="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
809
|
+
-o ConnectTimeout=30 -o BatchMode=yes -i ~/.ssh/id_rsa remo@$HOST"
|
|
810
|
+
|
|
811
|
+
# Plant a project dir without a .devcontainer so we exercise the
|
|
812
|
+
# host-side branches of project-launch (no docker dance needed in CI).
|
|
813
|
+
$SSH_CMD "mkdir -p ~/projects/smoke-proj && \
|
|
814
|
+
echo 'placeholder' > ~/projects/smoke-proj/README"
|
|
815
|
+
|
|
816
|
+
# --- Foreground --exec on a no-devcontainer project (Case 4) ---
|
|
817
|
+
OUT=$(uv run remo shell -p smoke-proj --exec 'echo hello-$REMO_PROJECT-$REMO_INSTANCE && pwd' 2>&1)
|
|
818
|
+
echo "$OUT"
|
|
819
|
+
echo "$OUT" | grep -q "hello-smoke-proj-" || {
|
|
820
|
+
echo "FAIL: --exec did not run / env vars missing"; exit 1; }
|
|
821
|
+
echo "$OUT" | grep -q "/projects/smoke-proj" || {
|
|
822
|
+
echo "FAIL: --exec did not cd into project dir"; exit 1; }
|
|
823
|
+
echo "remo shell -p --exec (foreground): PASS"
|
|
824
|
+
|
|
825
|
+
# --- Detached --exec writes log file and returns immediately ---
|
|
826
|
+
DETACH_OUT=$(uv run remo shell -p smoke-proj --detach \
|
|
827
|
+
--exec 'sleep 1 && echo detached-marker-$REMO_PROJECT' 2>&1)
|
|
828
|
+
echo "$DETACH_OUT"
|
|
829
|
+
echo "$DETACH_OUT" | grep -q "Launched detached" || {
|
|
830
|
+
echo "FAIL: detach didn't print launched message"; exit 1; }
|
|
831
|
+
# Give the background command a moment to finish
|
|
832
|
+
sleep 3
|
|
833
|
+
LOG=$($SSH_CMD "cat ~/.local/state/remo/smoke-proj.log")
|
|
834
|
+
echo "log contents:"; echo "$LOG"
|
|
835
|
+
echo "$LOG" | grep -q "detached-marker-smoke-proj" || {
|
|
836
|
+
echo "FAIL: detached command did not run / log missing marker"; exit 1; }
|
|
837
|
+
echo "remo shell -p --detach --exec: PASS"
|
|
838
|
+
|
|
839
|
+
# --- Validation: --exec without -p exits 2 ---
|
|
840
|
+
if uv run remo shell --exec 'true' 2>/dev/null; then
|
|
841
|
+
echo "FAIL: --exec without -p should have errored"; exit 1
|
|
842
|
+
fi
|
|
843
|
+
echo "validation: --exec without -p errors as expected"
|
|
844
|
+
|
|
845
|
+
# --- Validation: --detach without --exec exits 2 ---
|
|
846
|
+
if uv run remo shell -p smoke-proj --detach 2>/dev/null; then
|
|
847
|
+
echo "FAIL: --detach without --exec should have errored"; exit 1
|
|
848
|
+
fi
|
|
849
|
+
echo "validation: --detach without --exec errors as expected"
|
|
850
|
+
|
|
631
851
|
- name: Teardown
|
|
632
852
|
if: always()
|
|
633
853
|
run: |
|
|
@@ -11,6 +11,7 @@ See `.specify/memory/constitution.md` for project principles and non-negotiable
|
|
|
11
11
|
- N/A (Incus storage pools already configured by 001-bootstrap-incus-host) (002-incus-container-support)
|
|
12
12
|
- Python 3.11+ + Click (CLI framework), InquirerPy (interactive picker), boto3 (AWS, optional), hcloud (Hetzner, optional) (003-python-cli-rewrite)
|
|
13
13
|
- Flat file (`~/.config/remo/known_hosts`, colon-delimited) (003-python-cli-rewrite)
|
|
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)
|
|
14
15
|
|
|
15
16
|
- Ansible 2.14+ / YAML + `ansible.builtin`, `community.general` (for zypper module) (001-bootstrap-incus-host)
|
|
16
17
|
|
|
@@ -131,10 +132,10 @@ Provider SDKs (boto3, hcloud) are lazy-imported with clear error messages if mis
|
|
|
131
132
|
- Ansible 2.14+ / YAML: Follow standard conventions plus Constitution principles
|
|
132
133
|
|
|
133
134
|
## Recent Changes
|
|
135
|
+
- 005-provider-snapshots: Added cross-provider snapshot CLI (`remo <P> snapshot {create,list,restore,delete}`) + destroy-time cleanup hook across Incus / Proxmox / AWS / Hetzner.
|
|
134
136
|
- 003-python-cli-rewrite: Added Python 3.11+ + Click (CLI framework), InquirerPy (interactive picker), boto3 (AWS, optional), hcloud (Hetzner, optional)
|
|
135
137
|
- 002-incus-container-support: Added Ansible 2.14+ / YAML + `ansible.builtin`, `community.general` (existing), Incus CLI (local)
|
|
136
138
|
|
|
137
|
-
- 001-bootstrap-incus-host: Added Ansible 2.14+ / YAML + `ansible.builtin`, `community.general` (for zypper module)
|
|
138
139
|
|
|
139
140
|
<!-- MANUAL ADDITIONS START -->
|
|
140
141
|
<!-- MANUAL ADDITIONS END -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remo-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Remote development environment CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -113,6 +113,42 @@ The `fzf`-powered menu shows your projects from `~/projects`:
|
|
|
113
113
|
- **c**: Clone a new repository
|
|
114
114
|
- **x**: Exit to shell
|
|
115
115
|
|
|
116
|
+
### Jump Straight to a Project
|
|
117
|
+
|
|
118
|
+
Skip the menu and land directly in a project (devcontainer auto-launches):
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
remo shell -p my-app
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Run a one-shot command inside the project's devcontainer instead of opening
|
|
125
|
+
a shell — quote the command as a single string:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
remo shell -p my-app --exec 'pytest -x'
|
|
129
|
+
remo shell -p my-app --exec 'claude --remote-control'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Fire-and-forget — kick off a command on the remote and exit SSH immediately:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
remo shell -p my-app --detach --exec 'claude remote-control --name remo-rc'
|
|
136
|
+
remo shell -p my-app --detach --exec './long-build.sh'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Detached output is captured to `~/.local/state/remo/<project>.log` on the
|
|
140
|
+
remote, so you can tail it later (`remo shell -p my-app --exec 'tail -f
|
|
141
|
+
~/.local/state/remo/my-app.log'`). The command's environment gets
|
|
142
|
+
`REMO_INSTANCE` and `REMO_PROJECT` exported automatically — handy for
|
|
143
|
+
deterministic naming, e.g.:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
remo shell -p my-app --detach --exec \
|
|
147
|
+
'claude remote-control --name "remo-$REMO_INSTANCE-$REMO_PROJECT"'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Then on your phone, open claude.ai/code and pick the session by name.
|
|
151
|
+
|
|
116
152
|
### Port Forwarding
|
|
117
153
|
|
|
118
154
|
Forward remote ports to your local machine during SSH sessions:
|
|
@@ -160,12 +196,43 @@ Every remo environment includes:
|
|
|
160
196
|
|
|
161
197
|
---
|
|
162
198
|
|
|
199
|
+
## Snapshots
|
|
200
|
+
|
|
201
|
+
Capture a point-in-time copy of an instance before a risky change, then roll
|
|
202
|
+
back if it breaks. Available on every provider, with the same command surface:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
remo <provider> snapshot create <instance> [--name NAME] [--description TEXT]
|
|
206
|
+
remo <provider> snapshot list [INSTANCE]
|
|
207
|
+
remo <provider> snapshot restore <instance> <snapshot> [-y]
|
|
208
|
+
remo <provider> snapshot delete <instance> <snapshot> [-y]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`--name` defaults to `remo-YYYYMMDD-HHMMSS`. `-y` / `--yes` bypasses the
|
|
212
|
+
confirm prompt on destructive operations.
|
|
213
|
+
|
|
214
|
+
| Provider | Create | Restore | Notes |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| **Incus** | seconds | in-place rollback (container stopped briefly) | Free; uses native `incus snapshot`. |
|
|
217
|
+
| **Proxmox** | seconds | in-place rollback (container stopped briefly) | Free; requires snapshot-capable rootfs storage (ZFS, LVM-thin, Btrfs, Ceph, NFS, CIFS). `dir` storage is rejected pre-flight. |
|
|
218
|
+
| **AWS** | async — several minutes | in-place EBS volume swap; stops the instance, swaps the root volume, restarts. Typically 2-5 min downtime. | Costs $ per GB-month in EBS. Pre-restore root volume is preserved as a tagged orphan — delete it manually once you've verified the restore. |
|
|
219
|
+
| **Hetzner** | async — several minutes | server rebuild from the snapshot image, in-place. Typically 1-2 min downtime. | Costs € per GB-month. |
|
|
220
|
+
|
|
221
|
+
`remo` does not estimate storage cost — check your provider's billing console.
|
|
222
|
+
|
|
223
|
+
The `destroy` command on each provider checks for existing snapshots first and
|
|
224
|
+
offers to clean them up. Decline and the snapshots remain (you'll be warned
|
|
225
|
+
they continue to incur storage costs on AWS/Hetzner).
|
|
226
|
+
|
|
163
227
|
## CLI Reference
|
|
164
228
|
|
|
165
229
|
```bash
|
|
166
230
|
# Connect to environment
|
|
167
231
|
remo shell # Auto-connect (or picker if multiple)
|
|
168
232
|
remo shell my-env # Connect to a specific environment
|
|
233
|
+
remo shell -p my-app # Skip the menu, jump to ~/projects/my-app
|
|
234
|
+
remo shell -p my-app --exec 'pytest -x' # Run command in devcontainer
|
|
235
|
+
remo shell -p my-app --detach --exec 'claude remote-control --name rc' # Fire and exit
|
|
169
236
|
remo shell -L 8080 # Shell + forward remote :8080 to local :8080
|
|
170
237
|
remo shell -L 9000:8080 # Shell + forward remote :8080 to local :9000
|
|
171
238
|
remo shell -L 8080 -L 3000 # Shell + forward multiple ports
|
|
@@ -223,6 +290,15 @@ remo proxmox update --name <n> --volume-size 40 --cores 4 --memory 4096
|
|
|
223
290
|
remo proxmox destroy --name <n> [--yes] [--purge] # Destroy container
|
|
224
291
|
remo proxmox bootstrap --host <node> # Verify node + download LXC template
|
|
225
292
|
|
|
293
|
+
# Snapshots (all four providers)
|
|
294
|
+
remo <provider> snapshot create <instance> # Auto-named
|
|
295
|
+
remo <provider> snapshot create <instance> --name pre-x --description "before upgrade"
|
|
296
|
+
remo <provider> snapshot list # All instances
|
|
297
|
+
remo <provider> snapshot list <instance> # One instance
|
|
298
|
+
remo <provider> snapshot restore <instance> <snap-name> [-y] # In-place rollback
|
|
299
|
+
remo <provider> snapshot delete <instance> <snap-name> [-y] # Remove
|
|
300
|
+
# `<provider> destroy` will list existing snapshots and offer to clean them up first.
|
|
301
|
+
|
|
226
302
|
# Updates
|
|
227
303
|
uv tool upgrade remo-cli # Update CLI to latest version
|
|
228
304
|
remo <platform> update # Update dev tools on remote
|
|
@@ -94,6 +94,42 @@ The `fzf`-powered menu shows your projects from `~/projects`:
|
|
|
94
94
|
- **c**: Clone a new repository
|
|
95
95
|
- **x**: Exit to shell
|
|
96
96
|
|
|
97
|
+
### Jump Straight to a Project
|
|
98
|
+
|
|
99
|
+
Skip the menu and land directly in a project (devcontainer auto-launches):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
remo shell -p my-app
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Run a one-shot command inside the project's devcontainer instead of opening
|
|
106
|
+
a shell — quote the command as a single string:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
remo shell -p my-app --exec 'pytest -x'
|
|
110
|
+
remo shell -p my-app --exec 'claude --remote-control'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Fire-and-forget — kick off a command on the remote and exit SSH immediately:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
remo shell -p my-app --detach --exec 'claude remote-control --name remo-rc'
|
|
117
|
+
remo shell -p my-app --detach --exec './long-build.sh'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Detached output is captured to `~/.local/state/remo/<project>.log` on the
|
|
121
|
+
remote, so you can tail it later (`remo shell -p my-app --exec 'tail -f
|
|
122
|
+
~/.local/state/remo/my-app.log'`). The command's environment gets
|
|
123
|
+
`REMO_INSTANCE` and `REMO_PROJECT` exported automatically — handy for
|
|
124
|
+
deterministic naming, e.g.:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
remo shell -p my-app --detach --exec \
|
|
128
|
+
'claude remote-control --name "remo-$REMO_INSTANCE-$REMO_PROJECT"'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Then on your phone, open claude.ai/code and pick the session by name.
|
|
132
|
+
|
|
97
133
|
### Port Forwarding
|
|
98
134
|
|
|
99
135
|
Forward remote ports to your local machine during SSH sessions:
|
|
@@ -141,12 +177,43 @@ Every remo environment includes:
|
|
|
141
177
|
|
|
142
178
|
---
|
|
143
179
|
|
|
180
|
+
## Snapshots
|
|
181
|
+
|
|
182
|
+
Capture a point-in-time copy of an instance before a risky change, then roll
|
|
183
|
+
back if it breaks. Available on every provider, with the same command surface:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
remo <provider> snapshot create <instance> [--name NAME] [--description TEXT]
|
|
187
|
+
remo <provider> snapshot list [INSTANCE]
|
|
188
|
+
remo <provider> snapshot restore <instance> <snapshot> [-y]
|
|
189
|
+
remo <provider> snapshot delete <instance> <snapshot> [-y]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`--name` defaults to `remo-YYYYMMDD-HHMMSS`. `-y` / `--yes` bypasses the
|
|
193
|
+
confirm prompt on destructive operations.
|
|
194
|
+
|
|
195
|
+
| Provider | Create | Restore | Notes |
|
|
196
|
+
|---|---|---|---|
|
|
197
|
+
| **Incus** | seconds | in-place rollback (container stopped briefly) | Free; uses native `incus snapshot`. |
|
|
198
|
+
| **Proxmox** | seconds | in-place rollback (container stopped briefly) | Free; requires snapshot-capable rootfs storage (ZFS, LVM-thin, Btrfs, Ceph, NFS, CIFS). `dir` storage is rejected pre-flight. |
|
|
199
|
+
| **AWS** | async — several minutes | in-place EBS volume swap; stops the instance, swaps the root volume, restarts. Typically 2-5 min downtime. | Costs $ per GB-month in EBS. Pre-restore root volume is preserved as a tagged orphan — delete it manually once you've verified the restore. |
|
|
200
|
+
| **Hetzner** | async — several minutes | server rebuild from the snapshot image, in-place. Typically 1-2 min downtime. | Costs € per GB-month. |
|
|
201
|
+
|
|
202
|
+
`remo` does not estimate storage cost — check your provider's billing console.
|
|
203
|
+
|
|
204
|
+
The `destroy` command on each provider checks for existing snapshots first and
|
|
205
|
+
offers to clean them up. Decline and the snapshots remain (you'll be warned
|
|
206
|
+
they continue to incur storage costs on AWS/Hetzner).
|
|
207
|
+
|
|
144
208
|
## CLI Reference
|
|
145
209
|
|
|
146
210
|
```bash
|
|
147
211
|
# Connect to environment
|
|
148
212
|
remo shell # Auto-connect (or picker if multiple)
|
|
149
213
|
remo shell my-env # Connect to a specific environment
|
|
214
|
+
remo shell -p my-app # Skip the menu, jump to ~/projects/my-app
|
|
215
|
+
remo shell -p my-app --exec 'pytest -x' # Run command in devcontainer
|
|
216
|
+
remo shell -p my-app --detach --exec 'claude remote-control --name rc' # Fire and exit
|
|
150
217
|
remo shell -L 8080 # Shell + forward remote :8080 to local :8080
|
|
151
218
|
remo shell -L 9000:8080 # Shell + forward remote :8080 to local :9000
|
|
152
219
|
remo shell -L 8080 -L 3000 # Shell + forward multiple ports
|
|
@@ -204,6 +271,15 @@ remo proxmox update --name <n> --volume-size 40 --cores 4 --memory 4096
|
|
|
204
271
|
remo proxmox destroy --name <n> [--yes] [--purge] # Destroy container
|
|
205
272
|
remo proxmox bootstrap --host <node> # Verify node + download LXC template
|
|
206
273
|
|
|
274
|
+
# Snapshots (all four providers)
|
|
275
|
+
remo <provider> snapshot create <instance> # Auto-named
|
|
276
|
+
remo <provider> snapshot create <instance> --name pre-x --description "before upgrade"
|
|
277
|
+
remo <provider> snapshot list # All instances
|
|
278
|
+
remo <provider> snapshot list <instance> # One instance
|
|
279
|
+
remo <provider> snapshot restore <instance> <snap-name> [-y] # In-place rollback
|
|
280
|
+
remo <provider> snapshot delete <instance> <snap-name> [-y] # Remove
|
|
281
|
+
# `<provider> destroy` will list existing snapshots and offer to clean them up first.
|
|
282
|
+
|
|
207
283
|
# Updates
|
|
208
284
|
uv tool upgrade remo-cli # Update CLI to latest version
|
|
209
285
|
remo <platform> update # Update dev tools on remote
|
|
@@ -330,13 +330,29 @@
|
|
|
330
330
|
echo "Entering devcontainer (exit to return to host shell)..."
|
|
331
331
|
echo ""
|
|
332
332
|
|
|
333
|
+
# If REMO_DEVCONTAINER_CMD is set (project-launch passthrough),
|
|
334
|
+
# run that command inside the devcontainer instead of dropping
|
|
335
|
+
# into the user's shell. Wrapped in `bash -lc` so the user's
|
|
336
|
+
# command gets variable expansion, pipes, &&, and PATH from
|
|
337
|
+
# the devcontainer's login profile.
|
|
338
|
+
if [[ -n "$REMO_DEVCONTAINER_CMD" ]]; then
|
|
339
|
+
_bash_flags="-lc"
|
|
340
|
+
_exec_cmd="$REMO_DEVCONTAINER_CMD"
|
|
341
|
+
else
|
|
342
|
+
_bash_flags="-c"
|
|
343
|
+
_exec_cmd='if command -v zsh &> /dev/null; then exec zsh; else exec bash; fi'
|
|
344
|
+
fi
|
|
345
|
+
|
|
333
346
|
# Run devcontainer shell (not exec, so we can stop container after)
|
|
334
|
-
if ! devcontainer exec --workspace-folder "$_project_dir"
|
|
335
|
-
|
|
347
|
+
if ! devcontainer exec --workspace-folder "$_project_dir" \
|
|
348
|
+
env REMO_INSTANCE="${REMO_INSTANCE:-$(hostname)}" \
|
|
349
|
+
REMO_PROJECT="$ZELLIJ_SESSION_NAME" \
|
|
350
|
+
/bin/bash $_bash_flags "$_exec_cmd"; then
|
|
336
351
|
echo ""
|
|
337
352
|
echo "devcontainer exec failed. [Press any key to continue]"
|
|
338
353
|
read -n 1 -s -r
|
|
339
354
|
fi
|
|
355
|
+
unset _exec_cmd _bash_flags
|
|
340
356
|
|
|
341
357
|
# After exiting devcontainer, stop the container to free resources
|
|
342
358
|
echo ""
|
|
@@ -393,6 +409,14 @@
|
|
|
393
409
|
group: "{{ remo_user }}"
|
|
394
410
|
mode: '0755'
|
|
395
411
|
|
|
412
|
+
- name: Install project-launch script
|
|
413
|
+
ansible.builtin.template:
|
|
414
|
+
src: project-launch.sh.j2
|
|
415
|
+
dest: "/home/{{ remo_user }}/.local/bin/project-launch"
|
|
416
|
+
owner: "{{ remo_user }}"
|
|
417
|
+
group: "{{ remo_user }}"
|
|
418
|
+
mode: '0755'
|
|
419
|
+
|
|
396
420
|
- name: Verify remo user exists
|
|
397
421
|
ansible.builtin.command: id {{ remo_user }}
|
|
398
422
|
register: user_setup_user_info
|