You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5e26c9b
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: CONTEXT.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -181,6 +181,8 @@ Genesis supports a VSCode Remote-SSH-like workflow: a lightweight headless daemo
181
181
182
182
**Design constraint — multiple remote connections:** Each SSH target gets a **unique per-target node name** (`genesis_remote_<id>@127.0.0.1`, derived from the target's slugified `id`) and a per-target systemd unit (`genesis-remote-<id>`) / launchd plist label (`com.genesis.remote.<id>`). This enables **multiple simultaneous remote connections** — each target connects to a distinct BEAM node name, so `Node.connect` can address multiple hosts. The SSH tunnel uses a dynamically-assigned local port (via `find_free_port/0`) that forwards to the remote daemon's fixed port 9000. The custom `EvoGit.EpmdDist` module maps each per-target node name to its tunnel's local port via `:persistent_term` so the VM's `port_please/2` callback resolves it correctly without an external EPMD process. The remote daemon's `RELEASE_NODE` is set per-target at launch time via `--setenv=RELEASE_NODE` (systemd-run) or an `EnvironmentVariables` dict (launchd plist), and `rel/genesis_remote/env.sh.eex` respects an existing `RELEASE_NODE` env var (`export RELEASE_NODE="${RELEASE_NODE:-...}"`).
183
183
184
+
**SSH tunnel readiness (connect-path invariant)**: `RemoteConnection.do_connect_distributed/1` polls the local forwarded port for readiness (`wait_for_tunnel/4`, 100 ms interval, 10 s budget) BEFORE calling `Node.connect/1` — `ssh -L` can take >1.5 s to start listening (a fixed 500 ms settle sleep used to race it → instant `:econnrefused` → `{:error, {:node_connect_failed, ...}}` on fast-connecting hosts). The poll fails fast when the ssh Port dies, draining its output into the error; timeout/death → `{:error, {:tunnel_not_ready, reason}}`. `connect/1`'s GenServer call timeout is `@connect_call_timeout_ms` (wait budget + 15 s) so callers can't get a raw 5 s `:timeout`. Diagnostics: no remote-daemon log entry for a connect attempt ⇒ tunnel/port race (not a daemon problem); a `"Invalid challenge reply"` error ⇒ cookie mismatch. See `apps/evo_git/CONTEXT.md` for details.
185
+
184
186
**Remote asset download**: `EvoGit.RemoteBootstrap.download_url/1` is deterministic and network-free, always returning the direct `https://genesis.evox.group/dl/genesis_remote_<platform>.tar.xz` URL — a Cloudflare worker "smart download" endpoint that auto-detects whether the user is in mainland China and proxies the GitHub release asset through the Cloudflare network when needed (`curl -fL`/`wget` follow redirects). **glibc is the default and ONLY published Linux variant — asset names are NEVER suffixed** (the musl build is disabled for now, so the `_glibc` suffix, the libc-probe machinery, and `effective_libc`/`detect_libc`/`probe_libc` were all removed from `EvoGit.RemoteBootstrap` / `EvoGit.RemoteConnection`; if musl is ever revived, the suffix logic must be reintroduced deliberately). The `version` in `{:ok, url, version}` is always `"latest"` (keys the local download cache). CI uploads unversioned names (`genesis_remote_<os>_<arch>.tar.xz` — build-desktop.yml; Linux tarballs come from the dedicated `build-linux-remote` job on ubuntu-22.04, darwin/windows from their platform jobs), so no asset matching is needed. **Do NOT reintroduce asset listing/matching or any suffix check** — the desktop (`genesis_desktop_*_*.tar.gz`) and remote (`genesis_remote_*_*.tar.xz`) tarballs share the `_<os>_<arch>` suffix shape, so a bare suffix match would select the wrong asset, and the API call is unnecessary. **Format**: the remote asset is `.tar.xz` (`genesis_remote_<platform>.tar.xz`, created with `tar -cJf` in CI, downloaded/extracted with `tar -xJf` on the remote host); the desktop portable archive is `.tar.gz`. All runner OSes and remote hosts (Linux/macOS) have xz-capable tar, so no fallback exists — xz failure fails loudly.
Copy file name to clipboardExpand all lines: apps/evo_git/CONTEXT.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -73,6 +73,10 @@ The SSH remote-bootstrap release asset is a **`.tar.xz`** archive: `genesis_remo
73
73
74
74
**Linux asset variants:** glibc is the default and only published Linux variant — remote tarball names are NEVER suffixed (`genesis_remote_linux_x64.tar.xz`, `genesis_remote_linux_arm64.tar.xz`). The musl build is disabled for now. The previous libc-variant machinery was removed: `detect_libc/1` (ldd parser), `probe_libc/1` (SSH `ldd --version` probe), the private `effective_libc/2` normalization, the `_glibc` suffix in asset names/URLs/cache paths, and the `libc` params threaded through `resolve_platform` → `download_tarball` → `download_url`/`cache_path` (`resolve_platform/2` now returns `{:ok, daemon_os, platform}`). Downloads always resolve the default unsuffixed glibc asset. On NixOS hosts the glibc-only Linux asset runs via on-the-fly patchelf patching during bootstrap (`EvoGit.RemoteBootstrap.nixos_patch_script/1` + the `:patching_binaries` stage) — no new asset variants; glibc stays the only published Linux variant.
75
75
76
+
### SSH Tunnel Readiness (connect-path invariant)
77
+
78
+
`EvoGit.RemoteConnection.do_connect_distributed/1` must NOT call `Node.connect/1` before the SSH tunnel's local forwarded port actually accepts TCP connections. An `ssh -L <local_port>:127.0.0.1:<dist_port> -N <target>` forward can take well over a second to start listening on the local port (measured ~1.5s), and a too-early `Node.connect` fails instantly with econnrefused → `{:error, {:node_connect_failed, ...}}` with NO remote-side log entry (the dist handshake never reaches the daemon — do not hunt for cookie/EpmdDist causes when the remote log shows nothing). The connect path therefore polls local-port TCP readiness via `wait_for_tunnel/4` (`@tunnel_poll_interval_ms` 100ms, `@tunnel_wait_timeout_ms` 10s budget), fails fast when the ssh Port dies (draining its output into the error for diagnosability), and returns `{:error, {:tunnel_not_ready, reason}, state}` on timeout/death. `connect/1`'s GenServer call timeout is `@connect_call_timeout_ms` (wait budget + 15s) so callers can't exit with a raw `:timeout` while the handler is legitimately waiting. `wait_for_tunnel/4` is `@doc false` public solely so tests can drive it with tiny timeouts/poll intervals (see `test/evo_git/remote_connection_test.exs`). Remote-side rejection ("Invalid challenge reply" in the daemon journal) means a COOKIE mismatch — a different failure mode with a visible remote log entry.
79
+
76
80
## Custom Agents & Model Selection
77
81
78
82
User-defined agents and per-agent model selection live in ONE file: `<config_dir>/agents.toml` (same dir as config.toml, next to `remote_connections.toml`). Per-node semantics: agents.toml is read from the config dir of the node executing the agent — local node for local tasks, remote host for tasks on an SSH remote node (the RPC edits the file on the node whose config is being edited). The scheduler has NO cached copy; `Dispatch.register_agent/7` calls `EvoGit.CustomAgents`/`EvoGit.CustomAgents.ModelSelector` directly per spawn (both are stat-validated-cache backed, so per-spawn cost is one `File.stat` + one `:persistent_term.get`).
0 commit comments