Skip to content

Updating

An update is a pinned image digest plus the migrations bundled with it. One script does the whole sequence, and the same script runs whether we host you or you host yourself.

Terminal window
./scripts/plugboard-update.sh 0.2.0

What the script does, and why each step is there

Section titled “What the script does, and why each step is there”
StepWhy
1. Pre-flightRefuses to start from an unhealthy stack. An update is not a repair tool, and starting from a broken state makes the failure impossible to attribute
2. BackupA dump, size-checked and integrity-checked before anything changes. An empty or truncated dump is worse than no dump, because it looks like a backup
3. PullBy digest where possible. Verifies the cosign signature if cosign is installed
4. MigrateForward-only and idempotent, so re-running is safe
5. SwapRecreates the application services on the new image
6. Health gate/health/ready must pass and the instance must report the version you asked for, within 120 seconds
7. RollbackOn any failure after step 4, the previous image comes back automatically

The version check in step 6 exists because a tag that silently resolved to the old image passes every other check and looks like a successful update.

The database is not rolled back automatically

Section titled “The database is not rolled back automatically”

Migrations are forward-only. An automatic restore would discard everything written between the backup and the failure, so if a migration is implicated the script tells you which dump to restore and stops.

That is a deliberate choice. Losing ten minutes of tickets to an automatic fix is worse than a pause.

  • Read the [changelog](https://github.com/samiossoftware/plugboard/blob/main/C HANGELOG.md). Releases marked with a migration warning change the schema. Treat those with more care and prefer a quiet window.

  • Confirm SECRETS_MASTER_KEY is backed up somewhere other than the server. A database restored without it cannot decrypt its own secrets.

  • Rehearse if you want to. --dry-run checks the stack and pulls the image without changing anything:

    Terminal window
    ./scripts/plugboard-update.sh 0.2.0 --dry-run

Tags can be moved by whoever controls the registry. A digest cannot. For production, pin the digest published in the release notes:

Terminal window
./scripts/plugboard-update.sh sha256:1a2b3c...

The script writes the pin itself, and it writes both halves. Compose builds its reference as ${PLUGBOARD_IMAGE}:${PLUGBOARD_VERSION}, so a digest is spelled by moving the marker into the image name and leaving the bare hex as the version:

PLUGBOARD_IMAGE=ghcr.io/samiossoftware/plugboard@sha256
PLUGBOARD_VERSION=1a2b3c...

A tag pin is the ordinary shape, and the script restores it on rollback:

PLUGBOARD_IMAGE=ghcr.io/samiossoftware/plugboard
PLUGBOARD_VERSION=0.2.0

Every release is signed, and each one publishes a list of the software it contains, which is what a supply-chain questionnaire is asking for.

The update script checks the signature for you when the verification tool is present, so most sites never do this by hand. If your policy requires you to, or you want to check a download before an update window:

Terminal window
cosign verify ghcr.io/samiossoftware/plugboard@sha256:1a2b3c... \
--certificate-identity-regexp '^https://github.com/samiossoftware/plugboard/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Terminal window
cosign download attestation ghcr.io/samiossoftware/plugboard@sha256:1a2b3c...

Portable bundles are covered by a published checksum file instead. The automatic updater checks it and refuses an archive that does not match, which matters because it runs unattended with privileges.

Terminal window
./scripts/plugboard-update.sh 0.1.0

The script prints this command with the right version at the end of every successful run, so the way back is always in your scrollback.

If a migration is implicated, restore the dump the script took in step 2 before rolling the image back. Restoring an older schema under a newer image will not work, and the script says so rather than guessing.

Terminal window
curl -s https://helpdesk.yourschool.org/api/health/version
{ "version": "0.1.0", "channel": "stable", "commit": "9f2c1ab44e01", "builtAt": "2026-08-01T09:14:22Z" }

The same information is on /api/health, and exposed to Prometheus as plugboard_build_info. These endpoints deliberately need no tenant and no database, so they still answer when the instance is unhealthy.

The on-premises bundle updates itself by default. Every night at 01:00 in the machine’s own local time it asks whether a newer release exists, downloads it, checks it against its published checksum, and stages it. The swap happens on restart, because replacing files a running process holds open is how updaters corrupt themselves, especially on Windows.

Nothing is applied during the day. An update restarts the service, and doing that while the service desk is in use is the surprise the schedule exists to avoid.

Terminal window
./plugboard update --check # is there one? change nothing
./plugboard update # get it now, do not wait for tonight

Tune or turn it off in .env:

Terminal window
AUTO_UPDATE=off # never update on its own
AUTO_UPDATE_HOUR=3 # a different local hour, 0-23
AUTO_UPDATE_RESTART=off # stage it, but apply at your next restart

An archive whose checksum does not match the release is never unpacked. This runs unattended with privileges, so “probably the right file” is not good enough.

The previous version is kept in .previous-version, so a bad update can be undone without another download.

Terminal window
git fetch --tags
git checkout v0.2.0
pnpm install
node scripts/plugboard.mjs migrate

Then restart the service. Take a database dump first. Migrations are forward-only and there is no automatic way back.

RELEASE_CHANNEL in .env is stable or beta. Beta deployments get releases one ring earlier and help catch problems before a release reaches everyone.

It changes which release we recommend to you. It never causes an automatic update on the Docker or source routes.

Nothing updates itself there. Every update is a decision someone makes, on a deployment holding real data.

We run the same script on the same sequence, after the release has soaked on our own instance and the public demo. You are told before a migration-bearing release, and you can ask to be moved to a later ring. See releases and update windows.