Upgrade and reset | shim

Upgrade and reset

Upgrade the console and extension together, back up and restore the database, and reset a browser whose enrollment needs to start over.

On this page

The console accepts reports only when its own extension version uploads them, and each report keeps the version that recorded it. There are no adapters for older versions, so the console and the extension come from the same release and are upgraded together.

Upgrade

Plan the console and the extension rollout for the same maintenance window, outside working hours.

  1. Confirm in Devices that every active browser is Online now and its policy is Up to date. Browsers sync every minute on their own.

  2. Extract the new release’s bundle on the console host and, in its directory, verify and load its image, which sets SHIM_IMAGE to the new image ID. Keep the previous bundle and its image until the upgrade is done: a rollback starts that image again. If the new bundle’s console.env has a line that /etc/shim/console.env lacks, add it.

  3. Back up the database.

  4. Apply the new release’s migrations from the new image:

    docker run --rm --pull=never --env-file /etc/shim/console.env \
      -v /etc/shim/db-ca.crt:/run/db-ca.crt:ro \
      "$SHIM_IMAGE" bun .output/migrate.js
  5. Replace the console container: run docker stop shim-console && docker rm shim-console, then start the new image as in Start the server.

  6. From the new bundle’s directory, run its deployment check. It confirms that the console answers its health check and serves the new release’s extension package.

  7. Managed browsers already using the production extension identity update themselves. On each browser’s next sync, within a minute, the console turns away its version and the extension requests an update check. The browser installs the new extension when that check runs and finds the package on your console; browsers limit how often they check, so some take longer. Later releases retain the identity introduced in 0.10.0 and need no reset. A browser with the earlier development identity needs a new installation and enrollment; see the 0.10.0 changelog. Its queued reports do not transfer to the new extension.

  8. Refresh open ChatGPT, Claude, Gemini, Grok and Perplexity tabs so they load the current content script.

  9. After a few minutes, confirm in Devices that active browsers show the new version.

Limit. Until the new extension reaches a browser, that browser keeps fetching and enforcing the policy but cannot report: the console turns away its heartbeats with HTTP 426, and Devices keeps showing the old version. For updates under the same extension ID, the browser keeps the reports it queued, including prompts matched in that window, and uploads them once the extension updates, each still showing the extension version that recorded it. The new version discards a queued report it can’t read, and the whole queue, readable reports included, when the rest of the browser’s saved shim data can’t be read; the extension’s settings say how many. A browser whose update check fails, because it can’t reach /extension/ on the console or the console has no package, stays on the old version and keeps queueing reports, up to 1,000 or 4 MiB, until it updates. The extension requests another check after every turned-away heartbeat, but the browser throttles these requests, so the next check can wait until the browser’s own schedule, every few hours, or until it restarts.

Back up and restore

Back up the database before every upgrade and on your own schedule, with pg_dump from PostgreSQL 17 or later in custom format. shim schedules no backups and keeps no copies: how often you back up decides how much a restore loses, and where you keep the copies decides whether one survives losing the database host. The console image has no PostgreSQL tools, so run it from the postgres:17 image or a host client. Enter the database password the way you entered the administrator password, with read -rs PGPASSWORD && export PGPASSWORD, then:

docker run --rm -e PGPASSWORD -v /etc/shim/db-ca.crt:/run/db-ca.crt:ro postgres:17 \
  pg_dump --format=custom \
  --dbname='postgresql://shim@db.example.com/shim?sslmode=verify-full&sslrootcert=/run/db-ca.crt' \
  > shim-$(date +%F).dump

The postgres:17 image trusts no certificate authority of its own, so mount the one that signed the database certificate, as in Deploy the console. Don’t add -t to docker run: a terminal corrupts the archive. docker run --rm -i postgres:17 pg_restore --list < shim-2026-10-13.dump lists what a backup holds.

To restore, create an empty database from template0 owned by the console’s database user, such as CREATE DATABASE shim_restored OWNER shim TEMPLATE template0;, and restore into it. --single-transaction restores everything or nothing:

docker run --rm -i -e PGPASSWORD -v /etc/shim/db-ca.crt:/run/db-ca.crt:ro postgres:17 \
  pg_restore --no-owner --no-privileges --single-transaction \
  --dbname='postgresql://shim@db.example.com/shim_restored?sslmode=verify-full&sslrootcert=/run/db-ca.crt' \
  < shim-2026-10-13.dump

Set DATABASE_URL in /etc/shim/console.env to the restored database, set SHIM_IMAGE to the image that will serve it and apply migrations from that image. Migrations the backup already has are skipped. A console container that is already running keeps the database it started with until you replace it. Then follow Reapply access changes made after the backup to start the console from that image. Keep the old database until the restored console works.

To roll back an upgrade, restore the backup you took before it into a new database, reapply access changes made since and serve it with the previous release’s image: in the previous bundle’s directory, set SHIM_IMAGE as in Verify and load the image. The backup already holds that release’s migrations. Chrome and Edge never install an older extension version, so browsers keep the newer extension. They keep fetching and enforcing the policy but can neither report to nor enroll with the previous console until you deploy a newer release: it turns away their heartbeats and enrollment with HTTP 426, and a 0.9.0 console refuses 0.10.0 report uploads with HTTP 400, because it doesn’t accept the version field 0.10.0 added. To undo an extension change, ship it as a new release with a higher version.

Limit. A restore returns the database to the moment of the backup. Requests, policy versions, teams and AI app labels saved after it are lost. A browser enrolled after it can no longer sync: reset it and enroll it again with a new one-time code, or, for a browser with an enrollment key, remove shim from its browser policy and add it back. An enrollment key created after the backup no longer exists, so create a new one and update your browser policy. Browsers enrolled before the backup keep their enrollment. Before you publish a policy on the restored console, reset any whose extension settings show a sync error or that Devices shows as Needs attention, or remove shim from their browser policy and add it back if they have an enrollment key: they hold reports for policy versions the restore removed.

Security. A backup holds everything the console stores, including captured prompts with their edited and masked versions, file names and findings, Send once explanations and account emails. Encrypt backups and restrict access to them as you do for the database.

Reapply access changes made after the backup

A restore brings back access as it stood at the backup. A member deactivated or made a viewer since has their old access back, an invitation removed since can sign in through SSO, a revoked enrollment key enrolls new browsers, and a revoked browser syncs and reports again. A password replaced since works again for any account the restored database lets sign in. A session that existed at the backup and hasn’t expired is valid again, even if it was ended since by signing out, a deactivation or a password reset; a restore doesn’t extend a session’s 24-hour lifetime.

The restored database, not today’s console, decides who is an administrator and whose password the reset can replace. Work through these steps as an administrator who was active at the backup: a member promoted or added after it isn’t one there. List what changed after the backup from your own records, or from the old console if it still runs, and reapply it before you open the restored console to anyone else:

  1. Keep every console server on the restored database stopped, as for a password reset.

  2. End every session the restore brought back. Everyone, you included, signs in again:

    docker run --rm -e PGPASSWORD -v /etc/shim/db-ca.crt:/run/db-ca.crt:ro postgres:17 \
      psql --dbname='postgresql://shim@db.example.com/shim_restored?sslmode=verify-full&sslrootcert=/run/db-ca.crt' \
      -c 'DELETE FROM session'

    It prints DELETE and the number of sessions it ended.

  3. If a password was replaced after the backup, replace it again: follow steps 2 and 3 of the password reset with /etc/shim/console.env naming the restored database and SHIM_IMAGE set to the image that will serve it. The reset takes only an active administrator with a password in the restored database. An account created after the backup isn’t in the restored database, so it can’t sign in there: leave it absent rather than create it again. When the reset refuses an account the restored database does hold, such as a viewer promoted and given a new password after the backup, don’t promote or reactivate that account to make the reset work; deactivate it in the next step. To roll back to 0.9.0, run the reset with SHIM_IMAGE set to the 0.10.0 image and serve the database with the 0.9.0 image afterwards: 0.9.0 images have no reset command, and both releases use the same database schema.

  4. Start the console on the restored database where only you can reach it, for example by letting your HTTPS proxy pass only your own address: replace the console container as in Start the server. Sign in, then:

    • Under Members, choose Make viewer for everyone made a viewer after the backup and Remove invitation for every invitation removed after it. If an invitation offers Deactivate instead, because that person has signed in since, deactivate them.
    • Deactivate everyone deactivated after the backup and every restored account whose password the reset refused in step 3.
    • Under Devices, revoke every enrollment key and browser revoked after the backup.
  5. Open the console to everyone once every account from step 3 has a successful reset, is absent from the restored database or is deactivated. If no administrator active in the restored database can sign in to finish these steps, keep the console closed.

Migrations

The migration command applies each migration once and records it in the schema_migrations table. 001-platform.sql creates accounts, organizations, named policies with versions, teams, enrollment, devices and activity, including prompt versions, file names and file findings. 002-app-days.sql adds the AI apps each browser opened per day. 003-app-labels.sql adds each organization’s AI app labels. 004-drop-prompt-expiry.sql drops activity_events.prompt_expires_at. 005-demo-organizations.sql adds organizations.demo_seeded_at, which marks the demo organizations shim seeds for its own showings, and lets policy versions be deleted only for those organizations. 006-enrollment-keys.sql adds enrollment keys. 007-console-accounts.sql adds member deactivation, keeps each account to one identity provider binding and adds the identity provider table the sign-in library requires, which stays empty.

Policy versions saved before 0.7.0 have no AI app discovery setting and the console can’t read them, so reset any database from an earlier release and bootstrap it again.

Reset a browser

Reset removes the browser’s local enrollment, cached policy and pending reports. Pending reports are lost. A browser with a managed enrollment key offers no reset: to start it over, remove shim from its browser policy, which uninstalls the extension and clears its storage, wait until chrome://extensions or edge://extensions no longer lists shim, and add it back; it then enrolls with the key as a new device.

  1. Open the extension settings and choose Reset this browser.
  2. Confirm with Reset enrollment. Keep enrollment or Escape cancels.
  3. Wait for the status to say the browser was reset, then enroll it with a new one-time code.

Security. Neither a reset nor removing shim from browser policy revokes the device in the console. The device stays registered until an administrator chooses Revoke access under Devices, and each new enrollment adds a device, so the old one counts toward the browser limit until you revoke it.