| name | smbcloud-deploy-rsync |
| description | Use when implementing or debugging smbCloud's generic rsync-based deployment path in `smbcloud-cli`, especially `rsync_deploy.rs`, workspace dependency wiring, SSH identity handling, remote path normalization, and parity with shell rsync commands like `rsync -a source/ host:path/`. |
smbCloud Deploy Rsync
Use this skill when work is about smbCloud's generic rsync deploy path in the Rust CLI, not the specialized Next.js SSR flow.
Applies to:
crates/cli/src/deploy/rsync_deploy.rs
- workspace dependency wiring in
Cargo.toml
- embedding the Rust rsync crate instead of shelling out to system
rsync
- reproducing command semantics like
rsync -a source/ host:path/
- SSH key and remote destination handling for smbCloud runners
Scope boundary
This skill is for the generic rsync deploy implementation in smbcloud-cli.
Do not use it for:
kind = "nextjs-ssr" local-build deploys
- PM2 restart logic for standalone Next.js bundles
- Nginx config for SSR apps
Those belong to the Next.js deploy skill.
Target behavior
The baseline command to replicate is:
rsync -a 5mb-dot-app/ rsync-deploy-id-11:apps/web/5mb.app/
Important semantics:
- trailing slash on the source means copy the contents of the directory
- trailing slash on the destination means sync into that directory
- archive mode preserves the usual rsync metadata behavior
- the remote target is
host:path, not a local filesystem path
When implementing in Rust, preserve these semantics explicitly. Do not assume path handling will match shell rsync unless you normalize it yourself.
Current smbCloud conventions
Generic rsync deploy uses smbCloud config and runner metadata.
Keep the deploy model straight:
Project = umbrella workspace in smbCloud (not deployable)
DeployRepo = the git repository inside that workspace
FrontendApp = the deployable app unit, identified by frontend_app_id in config
Deployment = one release event, linked to a FrontendApp via frontend_app_id
The canonical chain is: Deployment → FrontendApp → DeployRepo → Project
The CLI config still uses [project] / [[projects]] entries for deploy targets during the migration, but they should be thought of as app targets, not umbrella workspaces.
Expect these responsibilities:
- get the logged-in smbCloud user
- resolve the SSH identity path from
Config::ssh_key_path(user.id)
- determine the runner host, usually through
runner.rsync_host()
- build the remote destination from configured project
path
- deploy the current source tree or configured source directory
- pass
frontend_app_id into deployment tracking payloads so the API attributes the deploy to the correct FrontendApp — this should always be present in new configs
The user-visible output may still mention the SSH key path. Preserve useful diagnostics, but avoid misleading claims about git deploy.
Dependency wiring
When the task is to replace shell rsync with the Rust embedding crate:
- add the upstream rsync embedding crate at the workspace root
Cargo.toml
- add it as a workspace dependency in the consuming crate
- wire only the minimal surface needed by
rsync_deploy.rs
Prefer the embedding crate over copying CLI subprocess behavior when the goal is portability or tighter control.
Before coding, inspect:
- the embedding crate API surface
- whether it supports client mode directly
- how stdout/stderr and exit status are reported
- how remote-shell transport is configured, if at all
If the crate cannot actually express host:path SSH transport parity, say so explicitly instead of forcing a partial migration.
Implementation guidance
Path normalization
Be precise with trailing slashes.
Preferred rule:
- normalize local source to end with
/
- normalize remote destination to end with
/
That matches the intended rsync -a source/ host:path/ behavior.
SSH identity
The CLI currently resolves the private key path from the smbCloud user id.
If the embedded rsync client still needs an ssh transport command, keep the identity handling equivalent to the current shell behavior.
Typical concerns:
- identity file path
- strict host key checking
- known hosts file or pinned host key strategy
- batch mode / no password prompts
Failure reporting
Preserve actionable errors.
Good failures include:
- local source path missing
- remote project path missing in config
- SSH key missing
- embedded client non-zero exit status
- stderr emitted by the transfer implementation
Do not flatten all deploy failures into a generic message.
Behavior parity
If replacing subprocess rsync, confirm parity for:
- source contents vs source directory copying
- remote path handling
- recursive transfer
- deletion behavior, if smbCloud depends on it
- stdout/stderr capture
- non-zero status mapping
- deploy tracking posts against the workspace
project_id and includes frontend_app_id for app-level attribution — both should be present
Validation
Use small checks first.
Rust
cargo check -p smbcloud-cli
- targeted tests if added around path normalization or command construction
Behavioral
Verify the resulting implementation matches the shell command shape conceptually:
- source
dir/
- destination
host:path/
- ssh identity derived from smbCloud config
If the code still shells out to rsync, validate the exact arguments.
If it migrates to the embedding crate, validate equivalent output and error handling.
Common mistakes
- mixing this generic rsync deploy path with the specialized
nextjs-ssr flow
- losing trailing slash semantics and copying the wrong directory level
- adding the dependency in one
Cargo.toml but not wiring the workspace dependency correctly
- assuming the embedding crate automatically supports ssh remote syntax without checking
- hiding transport stderr that the user needs to debug deploy failures
- hardcoding local paths instead of using smbCloud config and user-derived SSH identity
- treating the workspace
project.id as the deployable app identity — always use frontend_app_id for app-level deploy tracking
- assuming
--copy-links is harmless: it follows every symlink, so one dangling
link in the source (e.g. a stray pnpm .pnpm/node_modules/<pkg> pointing at an
untraced version) makes rsync exit 23
(IO error encountered -- skipping file deletion). The nextjs-ssr path hits
this on .next/standalone/ — prune dangling links before transfer (or use
--copy-unsafe-links semantics). See the nextjs skill.