Upgrading and rollback
An upgrade moves the tree to a release, pins the three image tags that release
names, and deploys. Images are built only from tags, so VIDRA_CORE_TAG,
VIDRA_USER_TAG and VIDRA_SEARCH_TAG in the env file name exactly what runs.
How you do the first two steps depends on what is in /opt/vidra:
test -f /opt/vidra/vidra-bundle.manifest && echo "bundle tree" || echo "git checkout"
install.sh unpacks a release bundle by default, so a host installed that way
is a bundle tree. A host installed with --git, or from before bundles
existed, is a git checkout. Run the upgrade commands as the user that owns
the tree: deploy/provision.sh makes that the vidra user.
The three tags are not always the same one. A release can re-release vidra-core
alone and keep the frontend and search at their earlier tags: v0.7.5 pairs
vidra-core v0.7.5 with vidra-user and vidra-search v0.7.3. The pairing for each
release is the checked-in record releases/<tag>.json in
yegamble/vidra, and
deploy.sh refuses a set of tags no record pairs unless you pass
VIDRA_RELEASE_MAPPING=warn.
Upgrading from v0.7.5
v0.7.7 pairs vidra-core v0.7.7 and vidra-user v0.7.7 with vidra-search
v0.7.3: search was not re-released, and releases/v0.7.7.json records that
pairing. Read these seven points before you move a v0.7.5 host. Do point 3
first.
Go from v0.7.5 straight to v0.7.7. vidra-core v0.7.6 is withdrawn and marked
as a pre-release: its deployment bundle was built from an out-of-date tree, and
it has no vidra-user release and no release record, so neither the installer
nor vidra update offers it. Do not pin it by hand.
1. vidra update on v0.7.5 refuses this release, by design
The v0.7.5 vidra update checks that the release exists in all three
repositories. With search held at v0.7.3 it stops at that check, calls v0.7.7
"half cut" and tells you to publish the missing release. That is expected. It
refuses before it snapshots the env file, takes a dump or deploys, so nothing
has changed. Do not follow the "publish the missing release" advice, and do not
work around the refusal. Upgrade by hand instead:
-
Bundle tree (
/opt/vidra/vidra-bundle.manifestexists): the manual steps in If the script is older than this release below, withTAG=v0.7.7. They read the pairing from the record, so step 4 setsVIDRA_CORE_TAG=v0.7.7,VIDRA_USER_TAG=v0.7.7andVIDRA_SEARCH_TAG=v0.7.3. -
Git checkout:
sudo -u vidra ./deploy/pin-release.sh v0.7.7, thensudo -u vidra ./deploy/deploy.sh.vidra updateon a checkout moves the images but not the meta tree, so it would leave you on the old compose files and scripts. The v0.7.5pin-release.shwrites one tag into all three keys, so after it runs, check the result and put search back if it moved:grep -E '^VIDRA_(CORE|USER|SEARCH)_TAG=' env/production.env
# want: core v0.7.7, user v0.7.7, search v0.7.3Running
pin-release.sh v0.7.7a second time uses the v0.7.7 copy of the script, which writes each key from the release record.
2. Replace the vidra CLI yourself
Upgrading the tree never replaces /usr/local/bin/vidra, so after the steps
above vidra owner reset and vidra claim still answer "unknown command". Re-run
the installer with the release and no setup interview:
curl -fsSL https://raw.githubusercontent.com/yegamble/vidra/main/install.sh \
| sh -s -- --ref v0.7.7 --no-setup
It downloads the v0.7.7 CLI, verifies it against the release's SHA256SUMS and
installs it over /usr/local/bin/vidra. On a bundle tree that already exists it
leaves the tree and env/production.env exactly as they are, and prints a
warning that it did not upgrade the tree: expected, because you did that in
point 1. Add --yes if there is no terminal to confirm on.
3. Set up encrypted off-site backups before you upgrade
On v0.7.5, backup.sh reads BACKUP_RCLONE_REMOTE, BACKUP_S3_URI and
HEALTHCHECKS_URL only from the process environment, never from
env/production.env. A host that set them in the env file has been taking
local-only, unmonitored backups. From v0.7.7 backup.sh reads them from the env
file, so the upgrade starts uploading the dump and the config archive
(JWT_SECRET, every key-encryption key, the database and S3 passwords) to
whatever remote you configured, the next time the timer runs.
Before you upgrade, either set BACKUP_AGE_RECIPIENTS to an
age public key, or confirm your rclone remote is a
crypt remote. From v0.7.7, backup.sh refuses to upload the config archive
in plaintext unless BACKUP_AGE_RECIPIENTS is set, the remote is type = crypt, or you set BACKUP_OFFSITE_PLAINTEXT=true. The dump still uploads, but
the run exits non-zero. Details and the v0.7.5 behaviour are in
Backups. The backup script lives in the meta tree, so
it only changes when you move the tree in point 1.
4. Rolling back is per component
v0.7.5 is not uniform: it pairs vidra-core v0.7.5 with vidra-user and
vidra-search v0.7.3. The v0.7.7 rollback.sh reads that pairing from
releases/v0.7.5.json in your tree, so after point 1 the bare form sets all
three tags correctly and logs the pairing it used:
vidra rollback v0.7.5
# [rollback] releases/v0.7.5.json pairs core=v0.7.5 user=v0.7.3 search=v0.7.3
The rollback.sh in a tree older than v0.7.7 writes the bare tag into all three
keys. It then fails pulling a vidra-user v0.7.5 image that does not exist, while
the release you are leaving keeps serving. If your tree is older, or you want the
command to say exactly what it pins, name each component:
vidra rollback --core v0.7.5 --user v0.7.3 --search v0.7.3
Rollback changes the image tags and nothing in the database, so schema 151 stays in place. A v0.7.5 application runs against it, which is what the one-release compatibility policy in Rollback promises. After a rollback:
- Mail configuration saved in the admin panel is ignored. v0.7.5 has no such
panel, so outbound mail uses the
SMTP_*values in the env file again. The saved configuration stays in the database and applies again when you return to v0.7.7. - The six runtime settings revert to their env values.
channel_sync_interval_minutes,channel_sync_batch,channel_sync_cooldown_minutes,channel_sync_backoff_max_hours,live_recording_retention_hoursandaudit_log_retention_daysare new in v0.7.7; v0.7.5 readsCHANNEL_SYNC_INTERVAL,CHANNEL_SYNC_BATCH,CHANNEL_SYNC_COOLDOWN,CHANNEL_SYNC_BACKOFF_MAX,LIVE_RECORDING_RETENTIONandAUDIT_LOG_RETENTIONfrom the env file only.
5. The Content-Security-Policy is enforced
The frontend now enforces its policy; the one that ships with v0.7.5
(vidra-user v0.7.3) only reported violations. Custom
CSS or JavaScript that loads a third-party font, stylesheet, script or iframe is
blocked. To stop the blocking while you fix it, set CSP_REPORT_ONLY=true in
env/production.env and run vidra deploy, not vidra restart: restart
keeps the environment the container was created with. The full procedure is in
Content-Security-Policy. It needs the
v0.7.7 compose file, which is another reason a checkout host must move its meta
tree in point 1.
6. Custom JavaScript and CSS are owner-only; mail settings need your password
Saving non-empty custom JavaScript or CSS needs the instance owner. Any other
admin gets 403 owner_only; clearing the document stays open to every admin, so
an admin can still remove a script they did not approve. Saving the outbound-mail
configuration asks the admin for their own password (or a step-up token, for an
account with no password), even if they are already signed in.
7. Migration 150 to 151 is additive
The release runs migration 0151, which creates one table, mail_config, and
changes nothing in an existing table. deploy.sh takes its pre-deploy dump
first and runs the migration as its own step, as on every release.
Upgrade a git checkout
cd /opt/vidra
sudo -u vidra ./deploy/pin-release.sh vX.Y.Z # tree to vX.Y.Z, tags set from the release record
sudo -u vidra ./deploy/deploy.sh # dump → pull → gated migrate → up → probe
pin-release.sh moves the checkout, takes a snapshot of the env file first, and
writes the three tags from the release record, so you do not edit them by hand.
It refuses to run as root. A checkout older than v0.6.5 does not have the
script yet; the one-time manual move is in
deploy/README.md.
Upgrade a bundle tree
A bundle tree has no git pull, and bumping the tags alone is refused. The
bundle's vidra-bundle.manifest names the release the compose files and the
expected schema version belong to. If VIDRA_CORE_TAG differs from it,
deploy.sh stops in its pre-flight, before the dump, with nothing changed. The
upgrade is to unpack the new release's bundle, then set the tags, then deploy.
pin-release.sh does the first two in one command. Run both commands as the
user that owns the tree, from /opt/vidra:
cd /opt/vidra
sudo -u vidra ./deploy/pin-release.sh vX.Y.Z # pairing → download → verify → unpack → tags
sudo -u vidra ./deploy/deploy.sh # or: vidra deploy
pin-release.sh never deploys: it ends by printing next: ./deploy/deploy.sh
and the running stack is untouched until you run that. What it checks, in this
order, with the tree and the env file unchanged if any check fails:
- The pairing comes from a record. It reads
releases/<tag>.jsonfor the core, user and search tags and prints which tag each key will get. With no record it refuses to guess, because a uniform guess for a core-only release names images that do not exist. Retry with network access, or state it:./deploy/pin-release.sh vX.Y.Z --component-tag user=<tag> --component-tag search=<tag>. - The download is verified before anything is unpacked. It fetches
vidra-bundle_<core tag>.tar.gzandSHA256SUMSfrom the vidra-core release over HTTPS and stops on a missing entry or a checksum mismatch. - An unsafe archive is refused. It lists the members first and refuses an
absolute or
..path, a symlink, hardlink or device, an archive with novidra-bundle.manifest, and one that ships its ownenv/*.envordeploy/Caddyfile.local. - The copy is staged. The archive is unpacked into a scratch directory inside the tree and copied over only after every check passed, with the manifest last, so an interrupted copy leaves the old manifest in place.
- Your secrets are safe. The bundle contains no
env/secrets and noCaddyfile.local, and the script snapshots the env file outside the tree before it writes the three tags.
It refuses to run as root, because files unpacked by root leave root-owned
scripts in a tree the deploy user owns. A release cut before bundles existed has
no bundle to download, so the script stops there. Every later deploy.sh run
depends on the manifest and the tags agreeing, which is why one command writes
both. install.sh re-run on a bundle tree leaves it alone and only warns when
the newest release differs from what is installed; it never upgrades one.
If the script is older than this release
The bundle upgrade is new in pin-release.sh. A tree installed from a release
cut before it (v0.7.5 and earlier) still has the old script, and the old script
refuses a bundle tree with is not a git checkout. The first upgrade from such
a tree must use the manual steps below. They unpack the new bundle, which brings
the new pin-release.sh with it, so every upgrade after that is the two commands
above.
-
Read the pairing. The record is not always inside the bundle you are about to unpack, so read it from the repository:
TAG=vX.Y.Z # the release you are moving to
curl -fsSL "https://raw.githubusercontent.com/yegamble/vidra/main/releases/$TAG.json" -o "/tmp/$TAG.json"
python3 -c 'import json,sys; c=json.load(open(sys.argv[1]))["components"]; [print(k, c[k]["tag"]) for k in ("core","user","search")]' "/tmp/$TAG.json"Note the three tags. The bundle is a vidra-core asset and is named for the core tag.
-
Download that bundle with the release's
SHA256SUMSand verify it.cd /opt/vidra
CORE=vX.Y.Z # the core tag from the record
BASE="https://github.com/yegamble/vidra-core/releases/download/$CORE"
curl -fsSLO "$BASE/vidra-bundle_$CORE.tar.gz"
curl -fsSLO "$BASE/SHA256SUMS"
awk -v want="vidra-bundle_$CORE.tar.gz" '$2 == want || $2 == "*" want' SHA256SUMS | sha256sum -c -Do not continue unless that prints
OK. -
Unpack it over the tree, as the tree's owner.
sudo -u vidra tar -xzf "vidra-bundle_$CORE.tar.gz" -C /opt/vidra # overwrites tracked files, keeps yoursRun as root,
tarkeeps the archive's uid 0 on every file, which leaves root-owned scripts in a tree the deploy user owns. -
Set the three tags in
env/production.envto the ones from step 1:VIDRA_CORE_TAG,VIDRA_USER_TAGandVIDRA_SEARCH_TAG. -
Deploy.
sudo -u vidra ./deploy/deploy.sh # or: vidra deploy
Do steps 3 and 4 together: until both are done, deploy.sh stops in its
pre-flight because the manifest and the tags disagree.
vidra update
vidra update finds the newest release, pins it, runs deploy.sh and, in the
cases it can, puts the previous tags back if the health probes fail. vidra update --check prints the
current and latest release and changes nothing.
On a bundle tree, vidra update hands the whole job to
deploy/pin-release.sh, exactly as in
Upgrade a bundle tree. Without --tag it first asks
GitHub for the newest vidra-core release and prints which one it chose and from
which owner. It reads the owner from VIDRA_IMAGE_OWNER in your shell, not from
the env file, so on a fork you set it there or name the release with
--tag vX.Y.Z. A bundle already on the newest release is left alone, and a
bundle ahead of it (a prerelease) is refused rather than moved backwards. It
reads and writes nothing else before the script runs, and like the script it
does not deploy, so run vidra deploy next. vidra update --check prints the
bundle's release and the newest one and changes nothing. --yes and
--no-rollback are refused there because the script has no prompt or deploy to
apply them to. vidra v0.7.5 and earlier do not recognise a bundle tree:
vidra update there treats it like a checkout and rewrites all three tags in
the env file. If it then fails, it can print a restore.sh line naming the
newest dump already in backups/, which that run did not take and which may be
weeks old. Do not use it on a bundle tree until your installed vidra is
newer; see Upgrading from v0.7.5. Run vidra update --help
for the flags your installed version supports.
Rollback
# App only, no schema change:
./deploy/rollback.sh v0.4.0 # or: vidra rollback v0.4.0
# Across an incompatible schema change:
./deploy/compose.sh stop api frontend
./deploy/restore.sh backups/pre-deploy-<ts>.dump.gz
./deploy/rollback.sh v0.4.0
rollback.sh rewrites the three tag values, pulls, restarts and re-probes. It
does not touch the database, and that is only safe because of the
one-release schema-compatibility policy: release N−1's code must run against
release N's schema. That policy is mechanically enforced by migrate-lint.sh
and the schema-compat workflow — see
Production deployment.
A true schema rollback is "restore the pre-deploy dump", not "migrate down".
That is what the second form above does, and it is why deploy.sh refuses to
proceed when its pre-deploy dump fails.
Failed health probes leave the broken release running. deploy.sh reports it
and stops; deciding to roll back is yours.
The embedded-migrator tag floor
Both migration one-shots are the service image itself, running its compiled-in
migrate up. deploy.sh and rollback.sh carry a MIN_EMBEDDED_MIGRATE_TAG
constant and refuse a VIDRA_CORE_TAG or VIDRA_SEARCH_TAG below it.
The reason is a hang, not an error: an older image's main() ignores the
migrate up argv and starts an API server, so the one-shot never exits.
deploy.sh would hang mid-pipeline and rollback.sh inside up -d. A hang with
no message is worse than a refusal.
To run a component release older than the floor you must also check out the
meta-repo revision that shipped with it — the pre-embedded compose files drove a
separate migrator container with a bind-mounted migrations/ directory.
VIDRA_USER_TAG is not gated: the frontend has no migrator.
Raise the floor, never lower it.
Cutting a release
make release VERSION=v0.5.0 # all three repos; prompts first
make release VERSION=v0.5.0 CONFIRM=1 # same, unattended
make release VERSION=v0.5.0 REPOS="vidra-core" # one repo only
./deploy/release.sh --yes v0.5.0 # the script directly
vidra release --yes v0.5.0 # the CLI, which execs that script
Each component repo's publish-container.yml runs on release: published and
pushes ghcr.io/<owner>/<repo>:<tag> — so cutting the release is building
the image. release.sh creates the release in each repo, watches the resulting
workflow run to its conclusion, then verifies the image is really in GHCR. It
exits non-zero with a per-image summary if any repo fails, and it does not
deploy anything.
The meta-repo is tagged too — first, and without a release — because
vidra-core's release-assets.yml builds the deployment bundle by checking this
repo out at the same tag. Run release.sh from a clean main; it refuses to tag
a HEAD that is not already on origin/main.
Guards, all of which fire before the first release is created:
- the tag must match
^v[0-9]+\.[0-9]+\.[0-9]+$— the leadingvis what every image reference assumes; ghmust be authenticated;- the tag must not already exist in any target repo;
- HEAD must be an ancestor of
origin/main, and an existing meta tag of that name must point at that very commit; vidra-userneeds theNEXT_PUBLIC_API_BASE_URLrepository variable set, or its workflow refuses to build and there is no published image for any tag:gh variable set NEXT_PUBLIC_API_BASE_URL -R yegamble/vidra-user -b https://your.origin
Re-publishing an existing tag — a build that failed on a transient registry
error — needs no new release. Each publish-container.yml takes a
workflow_dispatch with a required tag input and refuses a tag that does not
exist, so a typo can never publish the default branch:
gh workflow run publish-container.yml -R yegamble/vidra-search -f tag=v0.5.0
Merge order, when the compose revision changes
The chain is bootstrap.sh → component checkouts → docker-compose.yml
include:s vidra-core's compose file. A host on a new compose revision runs
against whatever images the tags name, so component releases land before the
compose revision that requires them:
vidra-core— release the change.vidra-search— same.- the meta-repo revision, with any floors raised to the tags from 1 and 2, then
VIDRA_*_TAGbumped in the env file.