feat(scripts): remote devcontainer orchestration via just recipe (#70)#166
Open
feat(scripts): remote devcontainer orchestration via just recipe (#70)#166
Conversation
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
Refs: #152 Co-authored-by: Cursor <[email protected]>
Refs: #153 Co-authored-by: Cursor <[email protected]>
…RI stub Refs: #152 Co-authored-by: Cursor <[email protected]>
- CHANGELOG: keep both #152 and #153 entries - devc_remote_uri.py: keep full implementation from #153 Refs: #153 Co-authored-by: Cursor <[email protected]>
…e devcontainers (#153) (#155) ## Description Adds `scripts/devc_remote_uri.py` — a standalone Python module/CLI that builds the Cursor/VS Code nested authority URI for remote devcontainers. Part of #70. Opening Cursor/VS Code into a remote devcontainer requires constructing a `vscode-remote://` URI with hex-encoded JSON specs; Python handles this cleanly with stdlib only. ## Type of Change <!-- Mark the relevant option(s) with an 'x' --> - [x] `feat` -- New feature - [ ] `fix` -- Bug fix - [ ] `docs` -- Documentation only - [ ] `chore` -- Maintenance task (deps, config, etc.) - [ ] `refactor` -- Code restructuring (no behavior change) - [ ] `test` -- Adding or updating tests - [ ] `ci` -- CI/CD pipeline changes - [ ] `build` -- Build system or dependency changes - [ ] `revert` -- Reverts a previous commit - [ ] `style` -- Code style (formatting, whitespace) ### Modifiers - [ ] Breaking change (`!`) -- This change breaks backward compatibility ## Changes Made - **scripts/devc_remote_uri.py** (new): `hex_encode()`, `build_uri()`, CLI with argparse - **tests/test_devc_remote_uri.py** (new): 11 pytest unit tests (hex_encode, build_uri, CLI, edge cases) - **CHANGELOG.md**: Added entry under ## Unreleased ## Changelog Entry <!-- Paste the exact entry you added to CHANGELOG.md under ## Unreleased. If no changelog update is needed, write "No changelog needed" and explain why. Example: ### Added - **SSH agent forwarding** ([#42](#42)) - Forward host SSH agent into devcontainer for seamless git authentication --> ### Added - **devc_remote_uri.py — Cursor URI construction for remote devcontainers** ([#153](#153)) - Standalone Python module with `hex_encode()` and `build_uri()` for vscode-remote URIs - CLI: `devc_remote_uri.py <workspace_path> <ssh_host> <container_workspace>` prints URI to stdout - Stdlib only (json, argparse); called by devc-remote.sh (sibling sub-issue) ## Testing <!-- Describe the tests you ran and how to verify your changes --> - [x] Tests pass locally (`just test`) - [ ] Manual testing performed (describe below) ### Manual Testing Details <!-- If applicable, describe manual testing steps --> N/A — `uv run pytest tests/test_devc_remote_uri.py -v` passes (11 tests). `just lint` passes. ## Checklist <!-- Mark completed items with an 'x' --> - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`) - [x] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above) - [x] My changes generate no new warnings or errors - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published ## Additional Notes <!-- Any additional information, screenshots, or context that reviewers should know --> Design and implementation plan posted on issue #153. Sub-issue of #70 (remote devcontainer orchestration). Refs: #153
…e recipe - Fix open_editor() to read workspaceFolder from remote devcontainer.json - Call devc_remote_uri.py with correct positional arguments - Add devc-remote recipe to justfile.base for convenient access Refs: #70 Co-authored-by: Cursor <[email protected]>
Signed-off-by: gerchowl <[email protected]>
Change argument format from '--path' flag to SSH-style 'host:path' syntax: - Accept user@host:path or host:path format - Default to $HOME if no path specified - Update justfile recipe to match new format This follows familiar SSH conventions and simplifies usage. Refs: #70 Co-authored-by: Cursor <[email protected]>
When no path is specified, use ~ which will be expanded by the remote shell to the remote user's home directory, matching SSH conventions. Refs: #70 Co-authored-by: Cursor <[email protected]>
Refs: #70 Co-authored-by: Cursor <[email protected]>
Signed-off-by: gerchowl <[email protected]>
Refs: #186 Co-authored-by: Cursor <[email protected]>
Refs: #186 Co-authored-by: Cursor <[email protected]>
Refs: #186 Co-authored-by: Cursor <[email protected]>
Refs: #186 Co-authored-by: Cursor <[email protected]>
…-orchestration # Conflicts: # .github/actions/test-project/action.yml # CHANGELOG.md
- Add CI=true env var path test for check_agent_identity - Add setup-labels entry point availability test - Add invalid TOML error propagation test for load_blocklist - Strengthen assertion on contains_agent_fingerprint call args Refs: #217
…ig-utils' into feature/70-remote-devc-orchestration # Conflicts: # .github/actions/test-project/action.yml # packages/vig-utils/src/vig_utils/agent_blocklist.py # scripts/manifest.toml
- prepare_remote() writes socket path and stubs local compose override - read_compose_files() / compose_cmd_with_files() parse devcontainer.json - run_container_lifecycle() runs post-create/post-start inside container - Socket detection in preflight, CONTAINER_FRESH tracking - Fix Tailscale tag from tag:devcontainer to tag:devc Refs: #70
- setup-claude.sh with install/start subcommands, gated by CLAUDE_CODE_OAUTH_TOKEN - inject_claude_auth() in devc-remote.sh forwards local OAuth token to remote compose - Uses `claude setup-token` flow (sk-ant-oat01-..., valid 1 year) — no API key needed - Hooks into post-create.sh (install) and post-start.sh (start) - Commented example in docker-compose.local.yaml Refs: #70
…fig file Adds devc-remote.sh --bootstrap <ssh-host> that performs one-time remote setup: interactive config creation, GHCR auth forwarding, and devcontainer image build. Re-runs read existing config without re-prompting. Refs: #235
…vc-orchestration # Conflicts: # CHANGELOG.md # tests/bats/devc-remote.bats
…ote-devc-orchestration # Conflicts: # CHANGELOG.md # assets/workspace/scripts/devc-remote.sh # scripts/devc-remote.sh
This was referenced Mar 9, 2026
SSH drops empty string arguments and the remote shell expands ~ before the script sees it. Use _NONE_ and _DEFAULT_ sentinels to preserve empty branch and default path values through the SSH boundary. Refs: #243
Move forward_ghcr_auth (renamed from bootstrap_forward_ghcr_auth) into the normal main() flow after check_ssh. This ensures the remote always has valid GHCR credentials without requiring a separate --bootstrap step. The call is idempotent — overwrites with current local creds. Refs: #243
…te-devc recipe - check_unpushed_commits() blocks deploy when local commits aren't pushed - --force/-f auto-pushes before deploying (handles no-upstream branches too) - just remote-devc <host> auto-detects org/repo:branch from local git state - BATS tests for all check_unpushed_commits scenarios - Fix existing gh: target tests to mock git (avoid real repo interference) Refs: #246
…orking userspace-networking mode cannot intercept incoming connections, so --ssh was advertised but non-functional. Now setup-tailscale.sh uses real TUN when /dev/net/tun is available and warns otherwise. inject_tailscale_key adds devices + cap_add to remote compose. Template example updated with required entries. Refs: #70
…pose files When TAILSCALE_AUTHKEY already existed in docker-compose.local.yaml, inject_tailscale_key returned early without adding the required devices + cap_add entries for real TUN networking. Refs: #70
Expired ephemeral keys in docker-compose.local.yaml blocked redeploys because inject_tailscale_key skipped when any TAILSCALE_AUTHKEY existed. Now always regenerates via OAuth. Also prefers podman-compose (Python) over docker-compose bridge which drops devices/cap_add on podman <5. Refs: #70
Non-login SSH shells miss ~/.local/bin where podman-compose is typically installed via pip. Add PATH prefix to preflight and all remote compose invocations. Refs: #70
podman compose (docker-compose bridge) correctly passes devices/cap_add when compose files are specified via -f flags, which compose_cmd_with_files already does. The earlier failure was caused by a stale local Tailscale client, not by the compose tooling. Remove COMPOSE_TOOL detection, REMOTE_ENV_PREFIX, and ~/.local/bin PATH injection. Refs: #70
check_local_tailscale() verifies BackendState=Running and Self.Online before spending time on remote setup. Fails fast with actionable error messages. Also adds health check to WezTerm LEADER+s and LEADER+d via shared check_tailscale_health() helper with toast notifications. Refs: #70
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implements remote devcontainer orchestration: a single command (
just remote-devc <host>ordevc-remote.sh) that provisions a devcontainer on a remote host and connects your IDE to it. This enables developers to spin up devcontainers on powerful remote machines (GPU servers, cloud VMs) from their local terminal without manual SSH and compose steps.Key capabilities
gh:org/repo[:branch]targets — Clone a GitHub repo on the remote host and start its devcontainer in one command--bootstrapflag — One-time remote host setup (config file, GHCR auth, image build)--forceflag — Auto-push unpushed commits before deploying; guards against deploying stale code--open ssh|cursor|code|none— IDE-agnostic connection modes with auto-detectiondockerComposeFilefrom devcontainer.json, builds correct-fflagsImplementation
scripts/devc-remote.shscripts/devc_remote_uri.pyjustfile.baseremote-devcrecipe wrapping devc-remote.sh with local git state auto-detectionsetup-tailscale.shsetup-claude.shType of Change
feat-- New featureIssues
Closes #152, #153, #221, #230, #231, #232, #235, #236, #243
Refs: #70, #208, #246
Testing
just test)Manual Integration Test Results (#243)
36/39 items verified on a real remote host. Remaining 3 edge cases (low disk, missing compose, missing runtime) covered by unit tests.
Full test matrix (click to expand)
1. Core orchestration
devc-remote.sh myserver:~/Projects/fd5— SSH, preflight, compose up--open none— infra only, no IDE launch--open ssh— waits for Tailscale, prints hostname--open code— opens VS Code instead of Cursor--yesflag — auto-accepts prompts (reuse running container)2. Tailscale SSH integration (#208, #230)
TS_CLIENT_ID+TS_CLIENT_SECRETset — generates ephemeral key, injects into remote compose--open sshmode — pollstailscale status, prints hostname when ready3. Claude Code CLI (#70)
CLAUDE_CODE_OAUTH_TOKENset — injects token into remote composesetup-claude.sh installinside container — installs CLI, createsclaudeuserclaudewrapper auto-switches to non-root user when run as rootsetup-claude.sh start— refreshes workspace permissions4. Container lifecycle
post-create.shthenpost-start.shinside containerpost-start.sh(skips post-create)5.
--bootstrap(#235)projects_dir, creates config, forwards GHCR auth, clones devcontainer repo, builds image--bootstrap --yes— uses defaults without promptingGHCR_TOKENcopied to remote6.
gh:org/repo[:branch](#236)devc-remote.sh myserver gh:vig-os/fd5— clones to~/Projects/fd5, starts devcontainerdevc-remote.sh myserver gh:vig-os/fd5:feature/my-branch— clones and checks out branchdevc-remote.sh myserver:~/custom/path gh:vig-os/fd5— overrides clone location7. Compose file parsing
read_compose_files()correctly readsdockerComposeFilearray from devcontainer.jsoncompose_cmd_with_files()builds correct-fflags8. Edge cases
Bugs found and fixed during testing
~inremote_clone_project— fixed with sentinel values (17ca79f)~/.local/binadded to PATH for SSH compose commands (15120fb)Changelog Entry
See
CHANGELOG.md## Unreleasedsection — fully up to date.Checklist
CHANGELOG.mdin the[Unreleased]section