# Workstation → Dev VM migration (shared SSH development) **Purpose:** Move canonical day-to-day development from **`~/projects` on a laptop/WSL box** to **LXC 5700 (Dev VM, `192.168.11.59`)** under **`/srv/projects`**, so all contributors and tooling use the **same tree** over **SSH** (including Cursor Remote-SSH). **Canonical references:** [DEV_VM_GITOPS_PLAN.md](DEV_VM_GITOPS_PLAN.md), [DEV_VM_SSH_REMOTE_ACCESS.md](DEV_VM_SSH_REMOTE_ACCESS.md), `scripts/create-dev-vm-5700.sh`, `scripts/setup-dev-vm-users-and-gitea.sh`. --- ## 1. What moves vs what stays local | Item | Where it runs | |------|----------------| | **Source tree, builds, tests, Git** | **Dev VM** `/srv/projects` (after migration) | | **Cursor / IDE UI** | Your laptop — connect with **Remote-SSH** to the Dev VM (code executes on 5700) | | **Proxmox host shell** (`pct`, `qm`) | **SSH to the node** that runs CT 5700 (see `DEV_VM_SSH_REMOTE_ACCESS.md` — 5700 may live on **r630-04**, not r630-01) | Pyright/Cursor language services then analyze files **on the Dev VM** when you use Remote-SSH, which also reduces “huge local monorepo” indexing on WSL. --- ## 2. Preconditions 1. **CT 5700** exists, is started, **`sshd` reachable:** `ssh -o ConnectTimeout=5 "${DEV_VM_USER:-dev1}@${IP_DEV_VM:-192.168.11.59}" pwd` 2. **`/srv/projects`** exists and is writable by your dev user (group `dev` or ACLs per setup script). 3. **SSH key** for that user is loaded (`ssh-add -l`). 4. **Network:** Choose one path: - **LAN / VPN:** `192.168.11.59:22` (same as `IP_DEV_VM`). - **Cloudflare Tunnel + Access (FQDN):** **`ssh.dev.d-bis.org`** — does **not** work as plain `ssh user@ssh.dev.d-bis.org` to port 22 through the public proxy; use **`cloudflared access ssh`** (see below and [DEV_VM_SSH_REMOTE_ACCESS.md](DEV_VM_SSH_REMOTE_ACCESS.md)). - **UDM WAN forward:** e.g. **`76.53.10.40:22` → `192.168.11.59:22`** (restrict by allowlist) — then SSH to the **public IP/hostname** on port 22, not the Cloudflare tunnel hostname. ### 2.1 Probes (copy/paste) **LAN (works when this workstation is on VLAN 11 / VPN to `192.168.11.0/24`):** ```bash ssh -o BatchMode=yes -o ConnectTimeout=8 dev1@192.168.11.59 true && echo OK ``` **FQDN via Cloudflare Access (requires `cloudflared` on PATH + policy / service token):** ```bash ssh -o BatchMode=yes -o ConnectTimeout=25 \ -o ProxyCommand="cloudflared access ssh --hostname %h" \ dev1@ssh.dev.d-bis.org true && echo OK ``` **All-in-one script (from repo root):** ```bash ./scripts/deployment/probe-dev-vm-ssh.sh ``` **Note:** A plain `ssh dev1@ssh.dev.d-bis.org` without `ProxyCommand` typically **times out** or errors: the hostname resolves to **Cloudflare anycast**, not a raw open `:22` on the public Internet. That is expected for tunnel-routed SSH. ### 2.2 Install `cloudflared` on the workstation (for FQDN SSH / rsync) ```bash mkdir -p ~/bin ARCH=$(uname -m); case "$ARCH" in x86_64) CF_ARCH=amd64;; aarch64|arm64) CF_ARCH=arm64;; *) CF_ARCH=amd64;; esac curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${CF_ARCH}" -o ~/bin/cloudflared chmod +x ~/bin/cloudflared export PATH="$HOME/bin:$PATH" ``` Then configure **Cloudflare Access** (browser login or `CF_ACCESS_CLIENT_ID` / `CF_ACCESS_CLIENT_SECRET` per [DEV_VM_SSH_REMOTE_ACCESS.md](DEV_VM_SSH_REMOTE_ACCESS.md)). A **TLS handshake failure** from `cloudflared access ssh` usually means missing or invalid Access credentials. --- ## 3. One-time: create or verify the Dev VM From repo root (operator machine with SSH to a Proxmox node): ```bash bash scripts/create-dev-vm-5700.sh --dry-run # optional bash scripts/create-dev-vm-5700.sh ``` Inside **5700** (or via `pct exec 5700 -- …`): ```bash bash /path/to/proxmox/scripts/setup-dev-vm-users-and-gitea.sh ``` Add **all** developers’ public keys to `dev1`–`dev4` (or your chosen accounts). --- ## 4. Sync `~/projects` → `/srv/projects` (first migration) **Default** source is **`~/projects`** (matches `~/projects` on WSL when `HOME` is `/home/intlc`). Dry-run: ```bash cd ~/projects/proxmox ./scripts/deployment/sync-local-projects-to-dev-vm.sh --dry-run ``` Live sync (**default: no remote deletes** — safe for first migration; remote-only trees like `the_order` are kept): ```bash ./scripts/deployment/sync-local-projects-to-dev-vm.sh ``` **Mirror** the workstation onto the VM (removes remote files not present locally — use only after review): ```bash ./scripts/deployment/sync-local-projects-to-dev-vm.sh --delete-remote ``` **Over Cloudflare Access SSH** (requires `cloudflared` + Access policy / service token; set hostname to the tunnel FQDN): ```bash export PATH="$HOME/bin:$PATH" export RSYNC_RSH='ssh -o BatchMode=yes -o ConnectTimeout=30 -o ProxyCommand="cloudflared access ssh --hostname ssh.dev.d-bis.org"' export DEV_VM_HOST=ssh.dev.d-bis.org cd ~/projects/proxmox ./scripts/deployment/sync-local-projects-to-dev-vm.sh --dry-run ``` Environment overrides: ```bash SOURCE=/home/intlc/projects DEV_VM_USER=dev1 ./scripts/deployment/sync-local-projects-to-dev-vm.sh ``` **Behavior:** - **Preserves `.git`** (full migration with history). - **Excludes** `node_modules`, Python venvs, typical build outputs, and by default **`MEV_Bot`** and **`the-order`** (large trees). Set `SKIP_LARGE_LOCAL_TREES=0` to include them. - **Without `--delete-remote`**, rsync does **not** delete extra files on the Dev VM (recommended first pass). **Secrets:** `.env` files may sync; on the Dev VM run `chmod 600 .env` (and siblings). Prefer **Gitea + CI secrets** for long-term; do not commit `.env`. --- ## 5. Cursor: use Remote-SSH from now on 1. Install **Remote - SSH** (built into Cursor). 2. **SSH config** (`~/.ssh/config` on the laptop), example: ```sshconfig Host dev-vm HostName 192.168.11.59 User dev1 ForwardAgent yes ``` If you use **Cloudflare Access SSH**, follow [DEV_VM_SSH_REMOTE_ACCESS.md](DEV_VM_SSH_REMOTE_ACCESS.md) for `ProxyCommand` / `cloudflared`. 3. **Connect:** `Cursor` → **Remote-SSH** → **dev-vm** → open folder **`/srv/projects/proxmox`** (or the monorepo root you need). 4. **Terminal inside Cursor** will be **on 5700** — that is the intended “single place” for `git`, `pnpm`, `forge`, and operator scripts that need LAN RPC. --- ## 6. Git coordination (recommended) After the first sync: 1. Ensure **Gitea** on the Dev VM (or cluster) holds the **authoritative** remotes ([DEV_VM_GITOPS_PLAN.md](DEV_VM_GITOPS_PLAN.md) §6). 2. On the Dev VM: `git remote -v` in each repo; push **`main`** (or default branch) to Gitea. 3. **Stop** using the old WSL tree for shared work, or treat it as **read-only archive** after cutover to avoid drift. --- ## 7. Cutover checklist - [ ] 5700 running; `ssh dev1@192.168.11.59` works. - [ ] `/srv/projects` ready and backed up if it already had content. - [ ] `sync-local-projects-to-dev-vm.sh --dry-run` reviewed. - [ ] Full sync completed; large dirs (`SKIP_LARGE_LOCAL_TREES`) decided. - [ ] Remote `.env` permissions fixed; no secrets committed. - [ ] Cursor Remote-SSH opens `/srv/projects/proxmox` successfully. - [ ] `pnpm install` / `forge` smoke test on Dev VM for critical packages. - [ ] Team notified: **canonical path = Dev VM `/srv/projects`**. --- ## 8. Rollback Keep the original **`~/projects`** tree until the team confirms the Dev VM workflow. Rollback = continue editing locally; no automatic deletion of the workstation copy is performed by the sync script.