A Bash deployment and rollback tool for a Java service on Linux.
I built DeployLatch for my Operating Systems course at UBIT to practise Linux and Bash alongside Java. It manages one Java service on one Ubuntu VM, from installing an update to checking it and recovering if it fails.
Bash · Java · Ubuntu · systemd
SE-458 · Fourth semester · UBIT, University of Karachi
Demo · Workflow · Run it · Code
I tested a working update and automatic recovery on an Ubuntu VM in VMware. The three JARs come from one small Java application; v3 deliberately returns HTTP 503 so the recovery path can be demonstrated.
- Start with v1. The service is running and
/healthreturnsOK. - Deploy v2. The update passes; v1 becomes the manual rollback target.
- Attempt v3. The update fails; v2 is restored and verified.
View the terminal demonstration
The failed v3 command still returns a non-zero exit status: the requested update failed, even though recovery succeeded.
Screenshot and final service status
After recovery:
Current: v2
Previous: v1
Service: active
Health: OK
Saved Ubuntu run; click the image to inspect the commands and history at full size.
Each JAR stays in its own release directory. The current symbolic link selects the release systemd starts; previous identifies the manual rollback target. The Java process runs under a dedicated, non-root account.
An update passes only when /health returns OK and /version matches the requested release ID. A file lock prevents overlapping commands, and deployment outcomes are logged with UTC timestamps.
The existing setup builds on Windows with PowerShell and JDK 25, then runs on Ubuntu with Java 25, Bash, curl, flock, and systemd. SSH/SCP copies the prepared files to the VM.
Follow the Ubuntu setup guide → for the build, transfer, initial v1 installation, service account, and troubleshooting.
Build the JARs and run the demonstration
In Windows PowerShell, from the repository root:
.\scripts\build-demo.ps1This creates demo-app-v1.jar, demo-app-v2.jar, and demo-app-v3.jar in demo-app/dist/. Complete the initial Ubuntu setup before running the commands below.
On Ubuntu, from ~/deploylatch-staging/:
sudo bash deploylatch.sh deploy demo-app-v2.jar v2
sudo bash deploylatch.sh deploy demo-app-v3.jar v3
sudo bash deploylatch.sh status
sudo bash deploylatch.sh historyAfter the failed v3 update restores v2, rollback selects v1 and makes v2 the new rollback target:
sudo bash deploylatch.sh rollbackEvery deployment needs a unique release ID that matches the version packaged in its JAR. Old and failed release directories remain, so repeating the same ID is refused.
Start with the Bash controller and follow its calls into the service and HTTP checks.
- Bash controller — command handling, release links, verification, recovery, and history.
- Service unit — Java startup, service account, and restart behavior.
- Java application — local
/healthand/versionendpoints. - Build helper — compile once and package three test releases.
Commands, paths, and failure behavior
The operational commands require sudo and take the same exclusive lock. help prints usage without either.
deploy JAR RELEASE_ID— install and verify an update; recover on failure.rollback— select and verifyprevious; swap the targets on success.status— show selected releases, service state, and HTTP health.history— print the latest 20 deployment records.help— display command usage; no sudo required.
Releases live at /opt/deploylatch/releases/<id>/app.jar. The current, previous, and .deploy.lock entries sit under /opt/deploylatch/. Outcomes are appended to /var/log/deploylatch/deployments.log. For Java startup errors, use journalctl -u deploylatch-demo.service -n 40 --no-pager.
- Verification: up to eight attempts, with a two-second timeout per HTTP request and a one-second pause after an unsuccessful attempt.
- Link replacement: a temporary symlink is renamed over
current. This makes the pointer change atomic, not the whole deployment. - Invalid input: unreadable files, invalid IDs, and existing release IDs are refused before activation. The file check does not validate JAR contents.
- Failed update: restore and verify the old release. If no valid old release exists, attempt to stop the service, remove
current, and recordFAILED. - Recovery failure: record
ROLLBACK_FAILED. A failed manual rollback restores the original pointer and attempts a restart, without another health check of that original release.
Successful operations record SUCCESS or MANUAL_ROLLBACK; verified automatic recovery records ROLLED_BACK. A failed deployment keeps its non-zero exit status.
The Java service binds to 127.0.0.1:8080 and reads its version and health setting from a packaged release.properties file. Unknown paths return 404; non-GET methods on the endpoints return 405. Both /version and healthy /health responses use HTTP 200; v3's /health returns HTTP 503.
Scope: one service, brief restart downtime, and HTTP-level checks. Paths, port, and retries are fixed. Old releases are retained, and recovery after an interruption or machine failure is not guaranteed.
The project helped me connect permissions, symlinks, locking, and exit codes to a running service. The most useful lesson was checking process state, application health, and release identity separately: an active process can still be unhealthy or running the wrong version.
I'm Shaikh Mahad Ud Din. If you're working through Java services or Linux scripting too, I'd be glad to compare approaches. Found a problem? Open an issue with the command you ran and its output.
