remo-cli 2.0.0rc2__tar.gz → 2.0.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.0.0rc2 → remo_cli-2.0.0rc4}/.devcontainer/devcontainer.json +1 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/smoke-test.yml +175 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/CLAUDE.md +2 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/PKG-INFO +38 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/README.md +37 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/docs/aws.md +110 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/pyproject.toml +1 -1
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/checklists/requirements.md +36 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/contracts/cli-surface.md +160 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/data-model.md +114 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/plan.md +101 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/quickstart.md +161 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/research.md +132 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/spec.md +181 -0
- remo_cli-2.0.0rc4/specs/005-provider-snapshots/tasks.md +286 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/main.py +18 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/providers/aws.py +125 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/providers/hetzner.py +105 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/providers/incus.py +126 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/providers/proxmox.py +167 -0
- remo_cli-2.0.0rc4/src/remo_cli/core/snapshot.py +170 -0
- remo_cli-2.0.0rc4/src/remo_cli/models/snapshot.py +43 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/providers/aws.py +505 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/providers/hetzner.py +335 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/providers/incus.py +348 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/providers/proxmox.py +349 -0
- remo_cli-2.0.0rc4/tests/unit/cli/providers/test_aws_snapshot.py +126 -0
- remo_cli-2.0.0rc4/tests/unit/cli/providers/test_hetzner_snapshot.py +129 -0
- remo_cli-2.0.0rc4/tests/unit/cli/providers/test_incus_snapshot.py +212 -0
- remo_cli-2.0.0rc4/tests/unit/cli/providers/test_proxmox_snapshot.py +158 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/cli/test_main.py +39 -0
- remo_cli-2.0.0rc4/tests/unit/core/test_snapshot.py +176 -0
- remo_cli-2.0.0rc4/tests/unit/providers/test_aws_snapshot.py +458 -0
- remo_cli-2.0.0rc4/tests/unit/providers/test_hetzner_snapshot.py +372 -0
- remo_cli-2.0.0rc4/tests/unit/providers/test_incus_snapshot.py +467 -0
- remo_cli-2.0.0rc4/tests/unit/providers/test_proxmox_snapshot.py +465 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/uv.lock +1 -1
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.analyze.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.checklist.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.clarify.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.constitution.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.implement.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.plan.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.specify.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.tasks.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.claude/commands/speckit.taskstoissues.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.dockerignore +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/dependabot.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/release.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.gitignore +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/CONTRIBUTING.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/Dockerfile +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/LICENSE +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/.ansible-lint +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/README.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/ansible.cfg +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/aws_site.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_site.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/requirements.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/docs/hetzner.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/docs/incus.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/docs/install.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/docs/proxmox.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/install.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/maverick.yaml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/project-menu.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/remo +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/run.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/ssh.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/conftest.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/integration/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.0.0rc2 → remo_cli-2.0.0rc4}/tests/unit/test_host_model.py +0 -0
|
@@ -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)
|
|
@@ -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: 2.0.
|
|
3
|
+
Version: 2.0.0rc4
|
|
4
4
|
Summary: Remote development environment CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -160,6 +160,34 @@ Every remo environment includes:
|
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
|
+
## Snapshots
|
|
164
|
+
|
|
165
|
+
Capture a point-in-time copy of an instance before a risky change, then roll
|
|
166
|
+
back if it breaks. Available on every provider, with the same command surface:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
remo <provider> snapshot create <instance> [--name NAME] [--description TEXT]
|
|
170
|
+
remo <provider> snapshot list [INSTANCE]
|
|
171
|
+
remo <provider> snapshot restore <instance> <snapshot> [-y]
|
|
172
|
+
remo <provider> snapshot delete <instance> <snapshot> [-y]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`--name` defaults to `remo-YYYYMMDD-HHMMSS`. `-y` / `--yes` bypasses the
|
|
176
|
+
confirm prompt on destructive operations.
|
|
177
|
+
|
|
178
|
+
| Provider | Create | Restore | Notes |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| **Incus** | seconds | in-place rollback (container stopped briefly) | Free; uses native `incus snapshot`. |
|
|
181
|
+
| **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. |
|
|
182
|
+
| **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. |
|
|
183
|
+
| **Hetzner** | async — several minutes | server rebuild from the snapshot image, in-place. Typically 1-2 min downtime. | Costs € per GB-month. |
|
|
184
|
+
|
|
185
|
+
`remo` does not estimate storage cost — check your provider's billing console.
|
|
186
|
+
|
|
187
|
+
The `destroy` command on each provider checks for existing snapshots first and
|
|
188
|
+
offers to clean them up. Decline and the snapshots remain (you'll be warned
|
|
189
|
+
they continue to incur storage costs on AWS/Hetzner).
|
|
190
|
+
|
|
163
191
|
## CLI Reference
|
|
164
192
|
|
|
165
193
|
```bash
|
|
@@ -223,6 +251,15 @@ remo proxmox update --name <n> --volume-size 40 --cores 4 --memory 4096
|
|
|
223
251
|
remo proxmox destroy --name <n> [--yes] [--purge] # Destroy container
|
|
224
252
|
remo proxmox bootstrap --host <node> # Verify node + download LXC template
|
|
225
253
|
|
|
254
|
+
# Snapshots (all four providers)
|
|
255
|
+
remo <provider> snapshot create <instance> # Auto-named
|
|
256
|
+
remo <provider> snapshot create <instance> --name pre-x --description "before upgrade"
|
|
257
|
+
remo <provider> snapshot list # All instances
|
|
258
|
+
remo <provider> snapshot list <instance> # One instance
|
|
259
|
+
remo <provider> snapshot restore <instance> <snap-name> [-y] # In-place rollback
|
|
260
|
+
remo <provider> snapshot delete <instance> <snap-name> [-y] # Remove
|
|
261
|
+
# `<provider> destroy` will list existing snapshots and offer to clean them up first.
|
|
262
|
+
|
|
226
263
|
# Updates
|
|
227
264
|
uv tool upgrade remo-cli # Update CLI to latest version
|
|
228
265
|
remo <platform> update # Update dev tools on remote
|
|
@@ -141,6 +141,34 @@ Every remo environment includes:
|
|
|
141
141
|
|
|
142
142
|
---
|
|
143
143
|
|
|
144
|
+
## Snapshots
|
|
145
|
+
|
|
146
|
+
Capture a point-in-time copy of an instance before a risky change, then roll
|
|
147
|
+
back if it breaks. Available on every provider, with the same command surface:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
remo <provider> snapshot create <instance> [--name NAME] [--description TEXT]
|
|
151
|
+
remo <provider> snapshot list [INSTANCE]
|
|
152
|
+
remo <provider> snapshot restore <instance> <snapshot> [-y]
|
|
153
|
+
remo <provider> snapshot delete <instance> <snapshot> [-y]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`--name` defaults to `remo-YYYYMMDD-HHMMSS`. `-y` / `--yes` bypasses the
|
|
157
|
+
confirm prompt on destructive operations.
|
|
158
|
+
|
|
159
|
+
| Provider | Create | Restore | Notes |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| **Incus** | seconds | in-place rollback (container stopped briefly) | Free; uses native `incus snapshot`. |
|
|
162
|
+
| **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. |
|
|
163
|
+
| **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. |
|
|
164
|
+
| **Hetzner** | async — several minutes | server rebuild from the snapshot image, in-place. Typically 1-2 min downtime. | Costs € per GB-month. |
|
|
165
|
+
|
|
166
|
+
`remo` does not estimate storage cost — check your provider's billing console.
|
|
167
|
+
|
|
168
|
+
The `destroy` command on each provider checks for existing snapshots first and
|
|
169
|
+
offers to clean them up. Decline and the snapshots remain (you'll be warned
|
|
170
|
+
they continue to incur storage costs on AWS/Hetzner).
|
|
171
|
+
|
|
144
172
|
## CLI Reference
|
|
145
173
|
|
|
146
174
|
```bash
|
|
@@ -204,6 +232,15 @@ remo proxmox update --name <n> --volume-size 40 --cores 4 --memory 4096
|
|
|
204
232
|
remo proxmox destroy --name <n> [--yes] [--purge] # Destroy container
|
|
205
233
|
remo proxmox bootstrap --host <node> # Verify node + download LXC template
|
|
206
234
|
|
|
235
|
+
# Snapshots (all four providers)
|
|
236
|
+
remo <provider> snapshot create <instance> # Auto-named
|
|
237
|
+
remo <provider> snapshot create <instance> --name pre-x --description "before upgrade"
|
|
238
|
+
remo <provider> snapshot list # All instances
|
|
239
|
+
remo <provider> snapshot list <instance> # One instance
|
|
240
|
+
remo <provider> snapshot restore <instance> <snap-name> [-y] # In-place rollback
|
|
241
|
+
remo <provider> snapshot delete <instance> <snap-name> [-y] # Remove
|
|
242
|
+
# `<provider> destroy` will list existing snapshots and offer to clean them up first.
|
|
243
|
+
|
|
207
244
|
# Updates
|
|
208
245
|
uv tool upgrade remo-cli # Update CLI to latest version
|
|
209
246
|
remo <platform> update # Update dev tools on remote
|
|
@@ -85,6 +85,14 @@ remo aws destroy --yes
|
|
|
85
85
|
|
|
86
86
|
# Destroy instance AND EBS (removes all data)
|
|
87
87
|
remo aws destroy --yes --remove-storage
|
|
88
|
+
|
|
89
|
+
# Snapshots
|
|
90
|
+
remo aws snapshot create <instance> # Auto-named (remo-YYYYMMDD-HHMMSS)
|
|
91
|
+
remo aws snapshot create <instance> --name pre-x --description "before upgrade"
|
|
92
|
+
remo aws snapshot list <instance> # Single instance
|
|
93
|
+
remo aws snapshot list # All registered AWS instances
|
|
94
|
+
remo aws snapshot restore <instance> <snap-name> [-y] # In-place EBS volume swap
|
|
95
|
+
remo aws snapshot delete <instance> <snap-name> [-y] # Remove snapshot
|
|
88
96
|
```
|
|
89
97
|
|
|
90
98
|
### Create Options
|
|
@@ -129,6 +137,25 @@ Available tools: `docker`, `user_setup`, `nodejs`, `devcontainers`, `github_cli`
|
|
|
129
137
|
|--------|-------------|
|
|
130
138
|
| `--name <name>` | Resource namespace (default: `$USER`) |
|
|
131
139
|
|
|
140
|
+
### Snapshot Options
|
|
141
|
+
|
|
142
|
+
| Option | Applies to | Description |
|
|
143
|
+
|--------|------------|-------------|
|
|
144
|
+
| `--name <snap>` | `create` | Snapshot name. Defaults to `remo-YYYYMMDD-HHMMSS`. Validated client-side: 1–40 chars, `^[A-Za-z0-9][A-Za-z0-9_-]*$`. |
|
|
145
|
+
| `--description <text>` | `create` | Free-text description stored on the EBS snapshot and shown in `list`. |
|
|
146
|
+
| `--region <region>` | all | AWS region. Defaults to the registered instance's region. |
|
|
147
|
+
| `--yes`, `-y` | `restore`, `delete` | Bypass the confirmation prompt. |
|
|
148
|
+
|
|
149
|
+
### Snapshot Behavior
|
|
150
|
+
|
|
151
|
+
* **Create is asynchronous.** The command returns within a few seconds with a "creation started; will take several minutes" hint. Use `snapshot list` to watch the `STATUS` column transition from `pending` to `available`. Restore and delete refuse to operate on pending snapshots.
|
|
152
|
+
* **Restore is an in-place volume swap.** The instance is stopped, the root EBS volume is detached, a new volume is created from the snapshot, attached as root, and the instance is restarted. Typical downtime: 2-5 minutes.
|
|
153
|
+
* **Restored volume preserves current size.** If you grew the EBS volume after taking the snapshot, the restored volume is created at the current (larger) size. The filesystem inside stays at the snapshot's recorded size until you run `sudo resize2fs $(findmnt -no SOURCE /)` inside the instance — `remo` prints this hint on restore.
|
|
154
|
+
* **Pre-restore volume is preserved as an orphan.** After a successful restore, the pre-restore root volume is *not* deleted; it stays in the AZ tagged `remo-restore-orphan=<ISO-8601-timestamp>` as a safety net. Once you've verified the restore is healthy, delete it manually with `aws ec2 delete-volume --volume-id <id>`. `remo` prints the exact command on restore.
|
|
155
|
+
* **Snapshots are scoped by volume ID.** Snapshots are listed by filtering on the *current* root volume ID, so if you destroy an instance and create a new one with the same name, the old snapshots will not appear in `list` (they become orphans, invisible to `remo`, manageable only via the AWS console).
|
|
156
|
+
* **No cost estimation.** `remo` does not estimate monthly EBS snapshot storage cost — check your AWS billing console.
|
|
157
|
+
* **Destroy-time cleanup.** `remo aws destroy` lists existing snapshots and offers to delete them as part of teardown. Declining (or passing `-y`) keeps the snapshots, which will continue to incur storage cost.
|
|
158
|
+
|
|
132
159
|
## Features
|
|
133
160
|
|
|
134
161
|
| Feature | Description |
|
|
@@ -180,6 +207,89 @@ df -h /home/remo
|
|
|
180
207
|
|
|
181
208
|
All instances use SSM Session Manager — no inbound ports are opened.
|
|
182
209
|
|
|
210
|
+
### IAM Permissions
|
|
211
|
+
|
|
212
|
+
The principal `remo` uses (your local IAM user, SSO session, or assumed role) needs the following EC2 actions. If you scoped your `remo` policy tightly, you'll see `UnauthorizedOperation` errors the first time you try a subcommand whose actions aren't in your policy.
|
|
213
|
+
|
|
214
|
+
| Subcommand | Required EC2 actions |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `create` | `RunInstances`, `DescribeInstances`, `DescribeImages`, `DescribeVolumes`, `CreateTags`, `CreateVolume`, `DescribeSecurityGroups`, `CreateSecurityGroup`, `AuthorizeSecurityGroupIngress`, `CreateKeyPair`, `ImportKeyPair`, `DescribeKeyPairs`, `RunInstances`, `iam:PassRole`, `iam:GetRole`, `iam:CreateRole`, `iam:AttachRolePolicy`, `iam:GetInstanceProfile`, `iam:CreateInstanceProfile`, `iam:AddRoleToInstanceProfile` |
|
|
217
|
+
| `destroy` | `DescribeInstances`, `TerminateInstances`, `DescribeVolumes`, `DeleteVolume`, `DescribeSecurityGroups`, `DeleteSecurityGroup`, `DescribeKeyPairs`, `DeleteKeyPair`, plus the IAM cleanup mirror of `create` |
|
|
218
|
+
| `update`, `info`, `list`, `sync` | `DescribeInstances`, `DescribeVolumes` (+ `ModifyVolume` and `DescribeVolumesModifications` for `update --volume-size`) |
|
|
219
|
+
| `stop`, `start`, `reboot` | `DescribeInstances`, `StopInstances` / `StartInstances` / `RebootInstances` |
|
|
220
|
+
| `snapshot create` | `DescribeInstances`, `DescribeVolumes`, `DescribeSnapshots`, `CreateSnapshot`, `CreateTags` |
|
|
221
|
+
| `snapshot list` | `DescribeInstances`, `DescribeVolumes`, `DescribeSnapshots` |
|
|
222
|
+
| `snapshot delete` | `DescribeInstances`, `DescribeVolumes`, `DescribeSnapshots`, `DeleteSnapshot` |
|
|
223
|
+
| `snapshot restore` | Everything in `snapshot list` **plus** `StopInstances`, `DetachVolume`, `CreateVolume`, `AttachVolume`, `StartInstances`, `CreateTags` |
|
|
224
|
+
| `destroy` (with existing snapshots, `-y` declined cleanup) | Adds `DescribeSnapshots` (to enumerate) and `DeleteSnapshot` (if cleanup accepted) on top of the regular `destroy` set |
|
|
225
|
+
|
|
226
|
+
> **Two AWS constraints to know.** First: `Describe*` actions can't be ARN- or tag-scoped — `Resource: "*"` is the only allowed form (read-side metadata leaks broader than mutation does). Second: snapshot ARNs are auto-generated (`snap-xxxxxx`) at create time, so prefix-on-the-ARN scoping doesn't help. Tag-based scoping is the practical path.
|
|
227
|
+
|
|
228
|
+
#### Snapshot-only policy with tag-locked mutations (attach as an inline policy to your `remo` role)
|
|
229
|
+
|
|
230
|
+
This is the recommended minimum. Reads stay broad (AWS limitation); mutations are locked to resources tagged `remo=true`, which is exactly what `remo` creates and never touches what it doesn't own.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"Version": "2012-10-17",
|
|
235
|
+
"Statement": [
|
|
236
|
+
{
|
|
237
|
+
"Sid": "CreateSnapshotRequiresRemoTag",
|
|
238
|
+
"Effect": "Allow",
|
|
239
|
+
"Action": "ec2:CreateSnapshot",
|
|
240
|
+
"Resource": "arn:aws:ec2:*:*:snapshot/*",
|
|
241
|
+
"Condition": {
|
|
242
|
+
"StringEquals": {"aws:RequestTag/remo": "true"}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"Sid": "CreateSnapshotFromAnyVolume",
|
|
247
|
+
"Effect": "Allow",
|
|
248
|
+
"Action": "ec2:CreateSnapshot",
|
|
249
|
+
"Resource": "arn:aws:ec2:*:*:volume/*"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"Sid": "TagSnapshotOnCreate",
|
|
253
|
+
"Effect": "Allow",
|
|
254
|
+
"Action": "ec2:CreateTags",
|
|
255
|
+
"Resource": "arn:aws:ec2:*:*:snapshot/*",
|
|
256
|
+
"Condition": {
|
|
257
|
+
"StringEquals": {"ec2:CreateAction": "CreateSnapshot"}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"Sid": "DeleteOnlyRemoSnapshots",
|
|
262
|
+
"Effect": "Allow",
|
|
263
|
+
"Action": "ec2:DeleteSnapshot",
|
|
264
|
+
"Resource": "arn:aws:ec2:*:*:snapshot/*",
|
|
265
|
+
"Condition": {
|
|
266
|
+
"StringEquals": {"ec2:ResourceTag/remo": "true"}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"Sid": "DescribeSnapshotsUnscopable",
|
|
271
|
+
"Effect": "Allow",
|
|
272
|
+
"Action": "ec2:DescribeSnapshots",
|
|
273
|
+
"Resource": "*"
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
What each `Sid` enforces:
|
|
280
|
+
|
|
281
|
+
| Sid | Enforces |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `CreateSnapshotRequiresRemoTag` | The CreateSnapshot call must include `remo=true` in `TagSpecifications` (`remo` always does this). |
|
|
284
|
+
| `CreateSnapshotFromAnyVolume` | Companion — `CreateSnapshot` evaluates against both source-volume and new-snapshot ARNs; both have to be allowed. |
|
|
285
|
+
| `TagSnapshotOnCreate` | `CreateTags` is only allowed as part of a `CreateSnapshot` request — prevents the role from re-tagging arbitrary resources. |
|
|
286
|
+
| `DeleteOnlyRemoSnapshots` | Can only delete snapshots tagged `remo=true`. Manual snapshots someone took via the console with no tag stay safe. |
|
|
287
|
+
| `DescribeSnapshotsUnscopable` | Unavoidable — AWS doesn't allow Describe* scoping; this lets `list` work. |
|
|
288
|
+
|
|
289
|
+
For `snapshot restore`, also add `ec2:StopInstances`, `ec2:DetachVolume`, `ec2:CreateVolume`, `ec2:AttachVolume`, `ec2:StartInstances`. Those can be tag-scoped on the instance / volume too — e.g., `Condition: {"StringLike": {"ec2:ResourceTag/Name": "remo-*"}}` to constrain to `remo`-named instances.
|
|
290
|
+
|
|
291
|
+
If you want a looser starting point and don't need the tag-locking (e.g., you fully trust who can assume the role), `Resource: "*"` on `ec2:CreateSnapshot`, `ec2:DescribeSnapshots`, `ec2:DeleteSnapshot`, `ec2:CreateTags` is a one-statement minimal version.
|
|
292
|
+
|
|
183
293
|
### What's Created
|
|
184
294
|
|
|
185
295
|
| Resource | Name Pattern | Description |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Specification Quality Checklist: Provider Snapshots
|
|
2
|
+
|
|
3
|
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
|
4
|
+
**Created**: 2026-05-24
|
|
5
|
+
**Feature**: [spec.md](../spec.md)
|
|
6
|
+
|
|
7
|
+
## Content Quality
|
|
8
|
+
|
|
9
|
+
- [x] No implementation details (languages, frameworks, APIs)
|
|
10
|
+
- [x] Focused on user value and business needs
|
|
11
|
+
- [x] Written for non-technical stakeholders
|
|
12
|
+
- [x] All mandatory sections completed
|
|
13
|
+
|
|
14
|
+
## Requirement Completeness
|
|
15
|
+
|
|
16
|
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
|
17
|
+
- [x] Requirements are testable and unambiguous
|
|
18
|
+
- [x] Success criteria are measurable
|
|
19
|
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
|
20
|
+
- [x] All acceptance scenarios are defined
|
|
21
|
+
- [x] Edge cases are identified
|
|
22
|
+
- [x] Scope is clearly bounded
|
|
23
|
+
- [x] Dependencies and assumptions identified
|
|
24
|
+
|
|
25
|
+
## Feature Readiness
|
|
26
|
+
|
|
27
|
+
- [x] All functional requirements have clear acceptance criteria
|
|
28
|
+
- [x] User scenarios cover primary flows
|
|
29
|
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
|
30
|
+
- [x] No implementation details leak into specification
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
|
35
|
+
- The spec mentions provider names (Incus, Proxmox, AWS, Hetzner) and provider-specific concepts (EBS, hcloud, pct, `--vmstate`, `dir` storage). These are kept because they describe the *user-facing scope* of the feature (which backends are covered) and the *user-visible behavioral differences* (latency, cost, downtime), not implementation choices. They are not framework or library references.
|
|
36
|
+
- Cost numbers and rate-per-GB figures are intentionally left as "estimated" with a 10% tolerance in SC-003, so the spec doesn't pin a specific provider price that will drift over time.
|