What
None of the three modes gives a deployment that is atomic at the directory level. Individual files are atomic (temp file plus posix-rename, internal/uploader/transfer.go), but the deployment as a whole is not:
- clean deletes everything under the target first, then uploads. Between the first
client.Remove and the last completed upload the target is incomplete or empty. For a web root that is a window of visible downtime, proportional to the size of the deploy.
- overlay and sync replace files one at a time, so there is a window where new HTML is live against old assets, or the reverse.
Why it matters
This is the main structural difference between "a tool that uploads files" and "a tool that deploys". The standard pattern everywhere else in deployment tooling (Capistrano, Deployer, and every hand-rolled shell script doing the same thing) is release directories plus a symlink swap: upload the new version alongside the old one, then flip a symlink in a single atomic operation.
easySFTP's audience is people deploying websites to SFTP-accessible hosting, which is the exact audience for whom "the site is broken for 40 seconds during every deploy" is the problem they wanted solved. docs/strategies.md presents clean mode as the way to get a target that exactly matches the local tree, without flagging that it does so by making the target temporarily wrong.
It is also worth being clear that this is in scope. CLAUDE.md rules out build steps, non-SFTP protocols and CI orchestration. A symlink swap is none of those: it is an SFTP operation (SSH_FXP_SYMLINK and posix-rename), performed by the uploader, that makes an SFTP deploy safer. It is squarely "make SFTP deploys more robust".
Suggested direction, and the open questions
The sketch:
- upload the full tree to a sibling release directory, for example
<target>-easysftp-<timestamp>
- create a symlink pointing at it
- atomically move that symlink onto the target path with
posix-rename, which replaces the old symlink in one operation
- remove older release directories, keeping N
pkg/sftp has Symlink and PosixRename, so the primitives exist. The questions are the hard part:
- The target must already be a symlink for this to work, or the first run has to convert a real directory into one. Converting is not atomic and is destructive if it goes wrong halfway. This probably has to be an explicit, documented one-time setup step by the user, not something easySFTP does silently to an existing directory.
- Symlink support is not universal. Chrooted
internal-sftp setups usually handle symlinks fine, but object-storage-backed SFTP front ends (AWS Transfer Family, Azure Blob SFTP) have no symlinks at all. This mode would have to detect that and refuse clearly rather than half-work. See the non-OpenSSH interop issue filed alongside this one.
- Web server configuration. Documents roots that resolve symlinks,
RewriteBase, PHP open_basedir, opcache keyed on realpath: several of these behave differently when the document root is a symlink. This needs real documentation, not a feature flag.
- Disk usage doubles (at least) during the deploy, and permanently if releases are retained. On quota-limited shared hosting, which is a large part of the audience, that is a serious constraint and needs a retention setting and a clear failure mode when the quota is hit mid-deploy.
- Interaction with
sync. Sync's manifest is per target directory. With rotating release directories, either the manifest moves with the release (and sync's incremental advantage disappears, since every release starts empty) or it lives outside them. Combining the two modes may simply not make sense, in which case that should be stated rather than left to be discovered.
- Whether this belongs in easySFTP at all, versus documenting how to achieve it with an existing mode plus one manual step. That is the real decision, and it is a scope call for the maintainer.
Filed as needs-check because of the last point. This is the largest feature idea in the analysis and the one most in need of a deliberate yes or no before any work starts. If the answer is no, docs/strategies.md should at least gain an explicit note that clean mode has a downtime window, so users can make an informed choice.
What
None of the three modes gives a deployment that is atomic at the directory level. Individual files are atomic (temp file plus
posix-rename,internal/uploader/transfer.go), but the deployment as a whole is not:client.Removeand the last completed upload the target is incomplete or empty. For a web root that is a window of visible downtime, proportional to the size of the deploy.Why it matters
This is the main structural difference between "a tool that uploads files" and "a tool that deploys". The standard pattern everywhere else in deployment tooling (Capistrano, Deployer, and every hand-rolled shell script doing the same thing) is release directories plus a symlink swap: upload the new version alongside the old one, then flip a symlink in a single atomic operation.
easySFTP's audience is people deploying websites to SFTP-accessible hosting, which is the exact audience for whom "the site is broken for 40 seconds during every deploy" is the problem they wanted solved.
docs/strategies.mdpresents clean mode as the way to get a target that exactly matches the local tree, without flagging that it does so by making the target temporarily wrong.It is also worth being clear that this is in scope. CLAUDE.md rules out build steps, non-SFTP protocols and CI orchestration. A symlink swap is none of those: it is an SFTP operation (
SSH_FXP_SYMLINKandposix-rename), performed by the uploader, that makes an SFTP deploy safer. It is squarely "make SFTP deploys more robust".Suggested direction, and the open questions
The sketch:
<target>-easysftp-<timestamp>posix-rename, which replaces the old symlink in one operationpkg/sftphasSymlinkandPosixRename, so the primitives exist. The questions are the hard part:internal-sftpsetups usually handle symlinks fine, but object-storage-backed SFTP front ends (AWS Transfer Family, Azure Blob SFTP) have no symlinks at all. This mode would have to detect that and refuse clearly rather than half-work. See the non-OpenSSH interop issue filed alongside this one.RewriteBase, PHPopen_basedir, opcache keyed on realpath: several of these behave differently when the document root is a symlink. This needs real documentation, not a feature flag.sync. Sync's manifest is per target directory. With rotating release directories, either the manifest moves with the release (and sync's incremental advantage disappears, since every release starts empty) or it lives outside them. Combining the two modes may simply not make sense, in which case that should be stated rather than left to be discovered.Filed as
needs-checkbecause of the last point. This is the largest feature idea in the analysis and the one most in need of a deliberate yes or no before any work starts. If the answer is no,docs/strategies.mdshould at least gain an explicit note that clean mode has a downtime window, so users can make an informed choice.