Skip to content

feat(add): provider-neutral remo add/remo remove for SSH-reachable hosts (014)#77

Merged
pofallon merged 3 commits into
mainfrom
014-register-ssh-host
Jul 22, 2026
Merged

feat(add): provider-neutral remo add/remo remove for SSH-reachable hosts (014)#77
pofallon merged 3 commits into
mainfrom
014-register-ssh-host

Conversation

@pofallon

Copy link
Copy Markdown
Contributor

Summary

Feature 014-register-ssh-host. Adds a provider-neutral remo add that registers a single SSH-reachable environment into the registry with only SSH access — no hypervisor host access, cloud credentials, or API token — plus remo remove to deregister it. Companion to 013's filtered sync: sync = bulk provider discovery; add = single manual registration.

Added hosts are a new registry type = "ssh" (access_mode = direct), so remo shell and remo cp connect through the existing direct-SSH path with no user-visible special-casing.

Design highlights

  • No registry format change (SC-007). The ssh type reuses KnownHost positional fields — instance_id = port, region = identity — via type-gated ssh_port/ssh_identity accessors. Existing 4/6/7-field provider lines parse unchanged.
  • One shared connection builder. build_ssh_opts emits -o Port=/identity only for type=ssh, so remo shell and remo cp both honor them (FR-005) while every provider's SSH argv stays byte-identical (a Proxmox numeric vmid in instance_id is never read as a port).
  • Collision safety (FR-010). A whole-registry check refuses to overwrite or shadow a provider entry — including incus/proxmox node/container short-names that remo shell <name> would resolve to. (This shadow case was caught by end-to-end validation and fixed with a regression test.)
  • Unmanaged degradation (FR-011). remo shell skips the tools/version check for ssh hosts → plain login shell instead of a bogus "Update tools?" prompt.
  • FR-012 guard. A shared guard_not_added_ssh_host() makes provider destroy/snapshot/resize against an added host fail with a clear message (20 call sites) rather than an opaque error.

Commands

remo add NAME [user@]host[:port] [--user U] [--port P] [--identity PATH] [--verify] [--yes]
remo remove NAME [--yes]
  • Re-adding an existing added host updates in place (confirm unless --yes), never duplicating.
  • --verify is a fail-closed SSH reachability probe: on failure it surfaces the error, writes nothing, exits non-zero. Without it, no network round-trip.
  • Un-bracketed IPv6 literals are rejected with guidance (bracketed [::1]:22 deferred).
  • remo remove is local-only (no remote connection/change) and refuses provider-managed hosts.

Testing

  • 105 new tests, one per conditional branch (Constitution II): parsing, collision refuse/update, IPv6 reject, port default/custom, identity present/absent, verify pass/fail, version-check skip, FR-012 guard.
  • Full suite: 1266 passed, 17 skipped. mypy + ruff clean.
  • End-to-end CLI validation against a temp registry (add/update/remove/collision/IPv6). The live-box SSH leg (shell/cp handshake to a real host) is the one manual spot-check left for a reviewer with a reachable box.

Spec-kit artifacts (spec/plan/research/data-model/contracts/quickstart/tasks) are included under specs/014-register-ssh-host/.

🤖 Generated with Claude Code

pofallon and others added 3 commits July 22, 2026 18:52
Feature 014 `remo add` — register a single SSH-reachable environment
(new registry type `ssh`, access_mode=direct) plus `remo remove`, so a
user with only SSH access (no hypervisor/cloud API) can `remo shell`/`cp`
into a box. Companion to 013's filtered sync.

Adds the full design set for the 014 branch:
- spec.md: 4 clarifications integrated (verify fail-closed; persisted
  --identity; reject un-bracketed IPv6; `remo remove` command)
- plan.md + research.md: reuse KnownHost positional serialization for the
  ssh type (port in instance_id, identity in region) — no format change,
  backward-compatible (SC-007); type-gated ssh_port/ssh_identity; wire
  port+identity into build_ssh_opts (shared by shell AND cp); skip the
  tools version-check for unmanaged hosts (FR-011); whole-registry
  collision scan (FR-010)
- data-model.md, contracts/{add,remove}-command.md, quickstart.md
- tasks.md: 24 TDD tasks, MVP = Phases 1-3; analyze remediations applied
  (FR-006 picker coverage, --verify moved to US3, cp coverage note)

Point .specify/feature.json at specs/014-register-ssh-host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAPB5GA4NVUgKFsVFVJDxN
…e hosts

Feature 014. Register a single SSH-reachable environment with only SSH access
(no hypervisor/cloud API) as a new registry type `ssh` (access_mode=direct),
then `remo shell`/`remo cp` into it like any other host.

Storage (no registry format change): the ssh type reuses KnownHost positional
fields — instance_id=port, region=identity — via type-gated `ssh_port`/
`ssh_identity` accessors. build_ssh_opts (shared by shell AND cp) emits
`-o Port=`/identity only for type=ssh, so every provider's argv is unchanged
(a proxmox vmid in instance_id is never read as a port).

Commands:
- `remo add NAME [user@]host[:port] [--user --port --identity --verify --yes]`
  create or in-place update (no dup); whole-registry collision check refuses to
  overwrite OR shadow a provider entry, including incus/proxmox node/container
  short-names (FR-010); un-bracketed IPv6 rejected (FR-013); `--verify` is a
  fail-closed reachability probe that writes nothing on failure (FR-014).
- `remo remove NAME [--yes]` local-only deregister, no remote call (FR-008);
  refuses provider-managed hosts (FR-009).

Also:
- `remo shell` skips the tools/version check for ssh hosts -> plain login shell
  for unmanaged boxes (FR-011).
- FR-012 guard: shared guard_not_added_ssh_host() makes provider destroy/
  snapshot/resize against an ssh host fail with a clear message (20 call sites).
- README documents add/remove; full test coverage per conditional branch
  (Constitution II). 105 new tests; full suite green (1266 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAPB5GA4NVUgKFsVFVJDxN
…check

Code-review fixes for feature 014:

- Registry line injection/corruption (FR-013): a ':' in --user (or user@)
  shifted every later field on reload, and a newline in user/host/identity
  could inject a forged registry line. Only the identity's ':' was checked.
  Add _reject_unsafe_field() rejecting ':' and control chars in user/host, and
  extend the identity check to control chars. Nothing is written on rejection.

- --verify fail-closed on unknown host keys: BatchMode=yes could not accept a
  new host key, so a reachable never-before-seen host failed "Host key
  verification failed" and was reported unreachable. The probe now adds
  StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null (reachability/auth
  check only; does not pre-seed ~/.ssh/known_hosts).

- Remove dead PROVIDER_TYPES constant.

+6 regression tests. Full suite: 1272 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAPB5GA4NVUgKFsVFVJDxN
@pofallon
pofallon merged commit 14b06dd into main Jul 22, 2026
13 of 14 checks passed
@pofallon
pofallon deleted the 014-register-ssh-host branch July 22, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant