Automated Upgrades & Rollbacks
Relevant source files
The following files were used as context for generating this wiki page:
The Automated Upgrades & Rollbacks system manages the lifecycle of the Puppypad server software by checking for, downloading, and installing new releases from GitHub. Because the application runs in a container without Docker socket access or network credentials for security reasons, it cannot upgrade itself directly. Instead, a host-side watcher identifies upgrade requests initiated in the admin interface and executes the deployment scripts.
This system ensures high availability by performing pre-upgrade backups and automated rollbacks. If the application fails healthy checks after a new version is started, the deployment script automatically reverts to the previous version and configuration.
System Architecture
The upgrade system operates through a communication bridge located in the data/upgrade directory. This shared folder allows the isolated application container to interact with the host-side upgrade-watch.sh script.
Component Interaction
The following diagram illustrates the flow from an administrator's request to the completion of an upgrade.
Communication Bridge
The system uses specific files within the data/upgrade directory to synchronize state between the host and the container.
| File | Direction | Purpose |
|---|---|---|
request | App -> Host | Contains commands like check or upgrade <tag>. |
state | Host -> App | Key-value pairs describing the current status and version info. |
notes | Host -> App | Release notes for the latest available version. |
log | Host -> App | The output of the last execution of install.sh. |
Sources: src/upgrade.js:5-10, scripts/upgrade-watch.sh:30-45
Upgrade Watcher Logic
The upgrade-watch.sh script runs every minute via a host-side cron job. It serves as the primary controller for detecting new releases and executing the upgrade process.
- Liveness reporting: The script updates the
seen_attimestamp in thestatefile every minute to prove it is running. - Request Handling: It checks for the existence of
data/upgrade/request. If found, it renames the file to.upgrade/requestto ensure atomic reading and prevent the app from tampering with the request during processing. - Validation: It only accepts requests for "check" or "upgrade" to the specific tag it has identified as the newest. This prevents a compromised app from forcing an upgrade to an arbitrary or malicious version.
- GitHub Polling: Every 60 minutes (configurable via
UPGRADE_CHECK_MINUTES), it fetches the latest release metadata from the GitHub API.
Sources: scripts/upgrade-watch.sh:5-20, scripts/upgrade-watch.sh:58-85, src/upgrade.js:31-35
Deployment & Rollback Process
The install.sh script performs the actual heavy lifting of an upgrade. It manages Docker images, files, and safety checks.
Sequence of Operations
The following sequence diagram details the safety-first approach taken during a version switch.
Sources: scripts/install.sh:135-195, README.md:108-118
Health Verification
The system considers an upgrade successful only if:
- The
appcontainer returns a200 OKfrom the/healthzendpoint within 30 attempts (approx. 90 seconds). - All services defined in
docker-compose.ymlremain in a "running" state for 15 consecutive seconds without restarts.
Sources: scripts/install.sh:163-186, src/server.js:255-260
Version Comparision
Version comparison is implemented in JavaScript to determine if an upgrade is actually required. The system compares tags following the vX.Y.Z format, parsing the numeric components.
const newer = (a, b) => {
const [x, y] = [a, b].map((t) => t.slice(1).split('.').map(Number));
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] > y[i];
return false;
};
Sources: src/upgrade.js:15-19, test/app.test.js:636-641
Security Safeguards
To prevent the upgrade mechanism from becoming an attack vector, several constraints are applied:
- Renaming Requests: The watcher moves the request out of the shared directory before processing to avoid symlink attacks or race conditions.
- Digest Enforcement: Upgrades use Docker image digests (recorded in the release bundle's
IMAGESfile) rather than just tags, ensuring that the pulled image exactly matches the code in the bundle. - Write Atomicity: Request files and state updates are written to temporary names and then renamed into place to prevent partial reads by the host or app.
- Restricted Commands: The host side only recognizes a minimal set of pre-defined commands.
Sources: src/upgrade.js:70-75, scripts/upgrade-watch.sh:20-28, scripts/install.sh:105-112
Summary
The Puppypad upgrade system provides a secure, host-mediated way to maintain an isolated containerized application. By leveraging a state-based file bridge and automated installation scripts, it allows for seamless version transitions with built-in disaster recovery through pre-upgrade backups and automated rollbacks based on comprehensive health checks.