A Bash tool for deploying a Java service on Linux and recovering from failed updates.
Operating Systems course project · UBIT, University of Karachi
Demo · Workflow · Run it · Project notes
The course demonstration ran on Ubuntu 26.04.1 LTS in VMware. Starting from v1, v2 deploys successfully. v3 deliberately returns HTTP 503, triggering a rollback to v2.
Click the screenshot to inspect the commands and deployment history at full size.
Text version of the demonstration
Deploying v2
Deployment successful: v2
Deploying v3
Deployment failed: v3
Restored v2
Current: v2
Previous: v1
Service: active
Health: OK
The v3 deployment command returns a non-zero exit status even after successful recovery: the requested update failed, while the old service was restored.
I built DeployLatch for my Operating Systems course to use the Linux and Bash skills from the lab alongside Java backend development. I wanted to work through how an application starts as a service, how an update is checked, and how to recover when something goes wrong.
The result is a small deployment tool for one Java service on one Ubuntu VM. It brings together shell scripting, Linux services, permissions, HTTP checks, and release management.
Each JAR gets its own release directory. A current symbolic link selects the running release; previous identifies the manual rollback target. systemd runs the application under a dedicated, non-root account.
The script accepts an update only when /health returns OK and /version matches the requested release ID. If startup or verification fails, it switches back to the old release, restarts it, and checks recovery. A file lock prevents concurrent deployments; outcomes are recorded with UTC timestamps.
The demonstrated setup builds the JARs on Windows with PowerShell and JDK 25, then runs them on Ubuntu with Java 25, Bash, curl, flock, and systemd. SSH/SCP transfers the files to the VM.
Build and run the course demo
In Windows PowerShell:
git clone https://github.com/codewithmahad/deploylatch.git
cd deploylatch
.\scripts\build-demo.ps1The build creates demo-app-v1.jar, demo-app-v2.jar, and demo-app-v3.jar in demo-app/dist/. v1 and v2 are healthy; v3 is intentionally unhealthy.
Next, follow the Ubuntu setup guide to transfer the files, install v1, create the service account, and enable the systemd service. The commands below assume that setup is complete.
On Ubuntu, deploy v2, then try the failing v3 release:
sudo bash ~/deploylatch-staging/deploylatch.sh deploy \
~/deploylatch-staging/demo-app-v2.jar v2
sudo bash ~/deploylatch-staging/deploylatch.sh deploy \
~/deploylatch-staging/demo-app-v3.jar v3After v3 fails and restores v2, inspect the result:
sudo bash ~/deploylatch-staging/deploylatch.sh status
sudo bash ~/deploylatch-staging/deploylatch.sh historyEach deployment needs a unique release ID. Existing release directories, including failed ones, are retained, so rerunning the same release ID is refused.
The operations guide contains the complete setup, file-transfer commands, manual rollback, and troubleshooting.
The Java app uses the JDK's built-in HTTP server. A release.properties file packaged into each JAR supplies its version and health behavior, so the same source produces all three demo releases.
It binds to the VM's loopback address, 127.0.0.1:8080. GET /health returns HTTP 200 with OK for v1/v2, or HTTP 503 with UNHEALTHY for v3. GET /version returns the release ID. Unknown paths return 404; other methods on these endpoints return 405.
Bash owns the deployment and recovery steps. systemd owns the Java process. Keeping the application small leaves the OS behavior easy to follow.
Use deploy, rollback, status, and history to manage releases and inspect results. Updates must pass health and version checks within eight attempts; a failed update returns a non-zero status even if recovery succeeds. The tool covers one local service with brief restart downtime, HTTP-level checks, and no guarantee of recovery after an interrupted deployment.
Project structure and entry points
deploylatch/
├── scripts/
│ ├── build-demo.ps1 # Build the three demo JARs on Windows
│ └── deploylatch.sh # Deploy, verify, recover, inspect history
├── systemd/
│ └── deploylatch-demo.service # Run the Java process as deploylatch
├── demo-app/src/main/java/dev/deploylatch/demo/
│ └── DemoServer.java # Health and version endpoints
├── docs/
│ ├── OPERATIONS.md # Ubuntu setup and demonstration
│ └── assets/ # README artwork and source notices
├── .gitattributes
├── .gitignore
└── README.md
Start with the Bash script, the service unit, or the Java application. Generated JARs and classes are excluded from Git.
Commands, release paths, and logs
All actions below run on Ubuntu using sudo bash ~/deploylatch-staging/deploylatch.sh followed by:
| Action | Result |
|---|---|
deploy JAR RELEASE_ID |
Install and verify a new release; recover on failure |
rollback |
Switch to previous; on success, make the replaced release the new rollback target |
status |
Show current/previous release, service state, and HTTP health |
history |
Show the last 20 deployment records |
help |
Print command usage; sudo is not required for help |
| Path | Purpose |
|---|---|
/opt/deploylatch/releases/<id>/app.jar |
Stored release |
/opt/deploylatch/current |
Active release link |
/opt/deploylatch/previous |
Manual rollback target |
/opt/deploylatch/.deploy.lock |
Deployment lock |
/var/log/deploylatch/deployments.log |
Timestamped deployment outcomes |
For Java startup errors or process output:
journalctl -u deploylatch-demo.service -n 40 --no-pagerVerification and failure behavior
Verification makes up to 8 attempts, with a 2-second timeout per HTTP request and a 1-second pause after each unsuccessful attempt. Both the health body and expected version must match. These are fixed values in the script.
The terminal demonstration shows a healthy update and automatic rollback. The table below describes the script's handling of these and other failure cases.
| Situation | Script behavior |
|---|---|
| Unreadable JAR, invalid ID, or existing release ID | Refuse the deployment before switching the active release |
| Another command holds the deployment lock | Exit instead of running concurrently |
| New service fails to start or pass verification | Restore the old release and verify it |
| The new deployment fails and no valid old release exists | Stop the service, remove current, and record FAILED |
| Automatic recovery also fails | Record ROLLBACK_FAILED and report failure |
| Manual rollback target fails | Restore the original active link, attempt a restart, and report failure |
A successful update records SUCCESS; successful automatic recovery records ROLLED_BACK; a successful manual rollback records MANUAL_ROLLBACK. Failed deployments keep a non-zero exit status.
Design choices and boundaries
- Separate release directories: retain old JARs rather than overwrite the running artifact.
- Atomic link replacement: prepare a temporary symlink, then rename it over
current. This makes the pointer change atomic, not the whole deployment. - Version-aware verification: a healthy response alone does not prove the requested release is running.
- Dedicated service user: deployment commands use sudo; the Java process runs as
deploylatch, withNoNewPrivileges=true. - Local HTTP service: loopback binding keeps the demonstration inside the VM.
Service restarts cause brief downtime. HTTP checks cover availability and release identity, not complete application correctness. The tool targets one local service, retains releases without automatic cleanup, and does not guarantee recovery from an interrupted deployment or a machine failure. Deployment paths, port, and retry settings are fixed in the source.
Tools and environment
Course context and learning
Operating Systems · SE-458
Department of Computer Science, UBIT, University of Karachi.
Built for my fourth-semester Operating Systems course in 2026. I handled the implementation, integration, and Ubuntu demonstration.
The project gave the lab concepts a practical purpose: file permissions protect the releases, symlinks select the active version, exit codes report failures, locking prevents overlapping commands, and systemd manages the process. The Java work connects HTTP responses and JAR packaging to those Linux operations.
I'm Shaikh Mahad Ud Din, @codewithmahad. I build Java backend projects and share what I learn. Working with Java or Linux, or have a project you'd like to build together? I'd be glad to connect.
