Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d307c1d
docs(remote-agents): specify the SSH provider contract
troyhoffman-oss Jul 28, 2026
1b14b6e
feat(backend-ssh): add the SSH remote-agent provider crate
troyhoffman-oss Jul 28, 2026
204ecf5
feat(desktop): discover and invoke backend providers on PATH
troyhoffman-oss Jul 29, 2026
489c1a5
feat(desktop): build a remote deploy payload from a provider record
troyhoffman-oss Jul 29, 2026
c8ec0df
feat(desktop): scope persona runtime sync to local records
troyhoffman-oss Jul 29, 2026
cf22ca9
feat(desktop): type and bind the backend-provider Tauri surface
troyhoffman-oss Jul 29, 2026
9ec77b7
feat(desktop): ask where an agent runs before anything else
troyhoffman-oss Jul 29, 2026
dd04b62
feat(desktop): one agent per exclusive host identity
troyhoffman-oss Jul 29, 2026
e8552f9
feat(settings): permanent Remote servers section under Settings > Agents
troyhoffman-oss Jul 29, 2026
b0a0cb2
ci(windows): run buzz-backend-ssh's tests on Windows
troyhoffman-oss Jul 29, 2026
5cfd992
test(desktop): cover provider-backed edit routing and the empty remot…
troyhoffman-oss Jul 29, 2026
2a495cc
feat(scripts): add a remote-host provisioning preflight
troyhoffman-oss Jul 29, 2026
449a5b9
fix(backend-ssh): key remote units by agent identity
troyhoffman-oss Jul 29, 2026
285924d
fix(desktop): derive provider ids without executable extensions
troyhoffman-oss Jul 29, 2026
9bc0f62
fix(backend-ssh): quote the resolved harness path in ExecStart
troyhoffman-oss Jul 29, 2026
7bbb95d
feat(backend-ssh): run remote units with NoNewPrivileges
troyhoffman-oss Jul 29, 2026
071974d
feat(desktop): disclose that remote agents lose team instructions
troyhoffman-oss Jul 29, 2026
d45c25e
docs(remote-agents): report the Git helper and the missing workspace
troyhoffman-oss Jul 29, 2026
41b8a62
docs(remote-agents): name the stale unit a rekeyed slug leaves behind
troyhoffman-oss Jul 29, 2026
e8a8e34
fix(backend-ssh): resolve harnesses from ~/.local/bin over non-intera…
troyhoffman-oss Jul 29, 2026
c4a0e16
fix(scripts): let the provisioning PATH check pass without a login shell
troyhoffman-oss Jul 29, 2026
879f57d
docs(remote-agents): note the observer needs a resolved owner
troyhoffman-oss Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,13 @@ jobs:
# Serial: windows_resolver_tests mutate process-global env
# (BUZZ_SHELL/GIT_BASH/SystemRoot) that SharedState::new reads.
run: cargo test -p buzz-dev-mcp --target $env:TARGET -- --test-threads=1
- name: Test (buzz-backend-ssh)
# The provider ships to a user PATH on every desktop platform, so its
# path resolution (tailscale.exe candidates, ssh.exe lookup) and JSON
# parsing only gate if the crate is tested ON Windows. Its shell-script
# execution tests are #[cfg(unix)] and cover the remote side, which is
# always POSIX.
run: cargo test -p buzz-backend-ssh --target $env:TARGET
# Smoke-test the new host-prereq contract: Git for Windows (which provides
# bash) is available on the runner, a shell command round-trips, and bash
# does NOT resolve from System32 (so WSL's launcher is never picked up).
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/buzz-audit",
"crates/buzz-acp",
"crates/buzz-agent",
"crates/buzz-backend-ssh",
"crates/sprig",
"crates/buzz-test-client",
"crates/buzz-ws-client",
Expand Down
6 changes: 6 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ test-unit:
# Gateway unit and black-box HTTP tests are infra-free. Postgres-backed
# contract/race tests run in the dedicated CI job below.
cargo nextest run -p buzz-push-gateway
# Remote-deploy provider (buzz-backend-ssh). Infra-free: the deploy
# tests execute the generated script against a local /bin/sh with a
# stubbed HOME, no network. This is the only place the shell-injection
# canary runs — the Windows job's copy of these tests is #[cfg(unix)]d
# out — so dropping this step lets an injection regression ship green.
cargo nextest run -p buzz-backend-ssh
else
./scripts/run-tests.sh unit
fi
Expand Down
31 changes: 31 additions & 0 deletions crates/buzz-backend-ssh/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "buzz-backend-ssh"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Buzz backend provider that deploys managed agents to a remote host over SSH"

# Deliberately binary-only and deliberately NOT bundled with the desktop app
# (not in `tauri.conf.json` externalBin, not in `scripts/bundle-sidecars.sh`).
# `discover_provider_candidates` prepends the app bundle's own directory to the
# provider search path, so shipping this inside the bundle would give every
# install an auto-discovered SSH-deploy capability and quietly undermine the
# "Only use providers from trusted sources" warning the desktop shows. It is a
# release artifact the user installs to `~/.local/bin`, which is already on the
# discovery path.
[[bin]]
name = "buzz-backend-ssh"
path = "src/main.rs"

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
zeroize = { workspace = true }
# Deploy can install `buzz-acp` on the host by streaming it inside the script
# that already travels on the SSH stdin channel. base64 is what keeps raw bytes
# from corrupting that stream; sha2 is what lets the host refuse a payload that
# arrived damaged. Both are already workspace dependencies.
base64 = { workspace = true }
sha2 = { workspace = true }
39 changes: 39 additions & 0 deletions crates/buzz-backend-ssh/assets/buzz-acp@.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[Unit]
Description=Buzz agent %i
After=network-online.target
Wants=network-online.target
# A long-running agent must never be rate-limited into staying down: a unit
# held by the start limiter looks exactly like an agent that silently died,
# and only `systemctl reset-failed` clears it.
StartLimitIntervalSec=0

[Service]
Type=simple
# The agent runs arbitrary code by design, so the SSH user's own privileges are
# the intended ceiling — but without this the harness can climb past them
# through any setuid/setgid binary on the host, or through passwordless sudo
# granted to that user. A VPS pilot exercised the harness, the Buzz CLI,
# NIP-OA owner-reviewed draft creation and repository branch pushes with this
# set; all remained functional.
#
# Deliberately the whole hardening delta. ProtectSystem/ProtectHome would also
# apply here, but the agent has no modeled workspace yet (see the REPOS /
# working-directory limitation in docs/remote-agents.md) — so until writable
# paths are something the protocol states, those directives would be guessing
# at which of the user's home an agent legitimately needs.
NoNewPrivileges=true
# Holds the agent's minted nsec. Written by the provider with umask 077 and
# chmod 600; systemd reads it as the owning user.
EnvironmentFile=%h/.config/buzz-acp/%i.env
# Absolute path, substituted at install time from the host's resolved
# `buzz-acp` and double-quoted there — systemd splits an unquoted value on
# whitespace, and the configurable `buzz-acp path on the server` may name a
# directory that contains some. systemd does not expand environment variables
# in the program position, and the shell indirection that would work around
# that is not worth adding to a unit whose environment carries a private key.
ExecStart=@BUZZ_ACP_BIN@
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
Loading