I've done this with fabric and rsync.
a yml file where machines are defined and all roles are defined a deploy user on all machines
packages were built on the deployment server. a new timestamp version was written in the "version_file"
rsync runs and copies all file. the symlink on the host systems gets changed to the new version and if something fails you can always go back one version based on the file name.
took me two weeks to write it that way but still it was fabric (python) and not capistrano (ruby)
and I actually used the linux kernel for the whole syncing and so on so the OPs guys could debug and easily trace those things in their domain.
stuff ;)