Files
Haven/docs/extension-updates.md
T
ancsemi 921601b2e6 Extension updates: publish the empty security blocklist and read it from haven-app.com
Without docs/blocklist.json every check failed closed, so the feature could
not be used. The updater now asks haven-app.com directly instead of going
through the github.io redirect, which stays allowed.
2026-10-03 15:38:57 -04:00

8.5 KiB

Extension updates

Haven can update plugins and themes from public GitHub releases. Updates are manual and administrator-only: Haven does not poll repositories or install an extension in the background.

Extensions without update metadata continue to load normally, but Haven does not check them for updates.

Updating an extension

Open Settings → Admin → Extension Updates and select Check for updates. Haven checks the repositories declared by the installed extensions and lists newer compatible versions.

An extension with invalid update metadata or local edits is shown with an error and cannot be updated or rolled back. Other extensions are still checked.

Before making a change, Haven shows the repository, installed and proposed versions, the GitHub release notes, and a security warning. Release notes are shown as escaped plain text. Select I understand, update to continue.

Haven checks that:

  • the current user is still an administrator;
  • the release is published, immutable, and not a prerelease;
  • the installed file has not changed since the update check;
  • the version is not on Haven's security blocklist;
  • the downloaded file has the SHA-256 checksum declared by its manifest; and
  • the downloaded metadata matches the installed extension and repository.

The existing file remains in place if any check fails. After a successful update, Haven keeps the previous file for rollback and asks connected users to reload.

Rolling back

When a previous version is available, an administrator can review and restore it from the same settings page. Rollback uses the same administrator, blocklist, local-file, and atomic-replacement checks as an update.

A backup is only offered on the Haven version where it was created. Restore an extension manually after upgrading Haven if its saved version is no longer eligible for rollback. A version listed on the security blocklist cannot be restored.

Extension maintainer guide

An updateable extension needs metadata in its source file and a matching haven-release.json asset in each GitHub release. Haven downloads the .plugin.js or .theme.css file directly; do not package it in an archive or use an install script.

Add update metadata

Add @id, @version, and @update-repo to the extension's leading comment block:

/**
 * @name Example Layout
 * @id org.example.layout
 * @version 1.1.0
 * @update-repo example-owner/haven-extensions
 */
Field Requirement
@id A stable, case-sensitive identifier containing lowercase letters, digits, dots, underscores, or hyphens. It must start with a letter or digit and cannot exceed 128 characters.
@version A complete, stable semantic version such as 1.1.0. Prerelease versions are not supported.
@update-repo The public GitHub owner/repository containing the releases. Do not use a URL.

Keep the ID and repository unchanged in future builds. Haven rejects a download that changes either value. Keep the extension filename stable as well, because Haven replaces the installed file without changing its name.

Build the release asset

Produce a single .plugin.js file for a plugin or .theme.css file for a theme. The built file must contain the same ID, version, and repository as its metadata and the release manifest.

Calculate SHA-256 from the exact bytes that will be uploaded. For example:

shasum -a 256 ExampleLayout.plugin.js

Do not modify or regenerate the asset after calculating the checksum.

Create haven-release.json

Add one entry for each extension shipped by the release:

{
  "schemaVersion": 1,
  "extensions": [
    {
      "id": "org.example.layout",
      "type": "plugin",
      "version": "1.1.0",
      "requires": { "haven": ">=4.5.0 <5.0.0" },
      "asset": "ExampleLayout.plugin.js",
      "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  ]
}
Field Requirement
schemaVersion The integer 1.
extensions A nonempty array containing no more than 100 entries. Each ID can occur once.
id The exact @id from the extension file.
type plugin or theme.
version The exact stable semantic version from the extension file.
requires.haven A semantic-version range describing the Haven versions tested with this build.
asset The exact release asset basename. It must end in .plugin.js for a plugin or .theme.css for a theme. Paths and URLs are rejected.
sha256 The lowercase, 64-character SHA-256 digest of the raw extension asset.

The release tag does not have to match the extension version. A repository may publish several extensions in one release, and each entry may have its own version and compatibility range. See the complete release manifest example.

Publish the GitHub release

  1. Create a release in the public repository named by @update-repo.
  2. Upload haven-release.json and every extension file named in the manifest as release assets.
  3. Add release notes. Haven shows the GitHub release body as plain text when an administrator reviews the update.
  4. Publish the release as a normal release, not a draft or prerelease.
  5. Make the release immutable in the repository's GitHub settings.

Haven ignores a release until all of these conditions are satisfied. It checks published releases and chooses the highest newer compatible extension version, so the GitHub “latest” release does not need to support every Haven version.

Treat a published asset and manifest as permanent. Publish a new immutable release for corrections instead of replacing an existing asset.

Test a release

Install the extension file in Haven's normal plugins/ or themes/ directory, then open Settings → Admin → Extension Updates and run a check. Confirm that Haven finds the expected version, displays the repository and release notes, installs the file, and offers the previous version for rollback.

If Haven reports a checksum mismatch, calculate the digest from the uploaded release asset and compare it with sha256 in haven-release.json. Metadata errors usually mean the ID, version, repository, asset name, extension type, or Haven compatibility range differs between the installed file, downloaded file, and manifest.

Security blocklist

Haven reads its blocklist from https://haven-app.com/blocklist.json (the repository's docs/blocklist.json) before checking, installing, or rolling back an extension. The old https://ancsemi.github.io/Haven/blocklist.json address redirects there and is still allowed. Both are restricted to the blocklist file. Each entry identifies a repository, extension ID, exact versions, and a reason:

{
  "schemaVersion": 1,
  "updatedAt": "2026-09-08T00:00:00Z",
  "blocked": [
    {
      "repo": "example-owner/haven-extensions",
      "id": "org.example.layout",
      "versions": ["1.0.1"],
      "reason": "This release has been withdrawn by its publisher."
    }
  ]
}

Repository matching is case-insensitive; IDs and versions are exact. Haven warns administrators when an installed version is listed and prevents listed versions from being installed or restored. It does not silently disable an extension that is already running.

If the blocklist is unavailable or malformed, existing extensions continue to run, but Haven prevents updates and rollbacks until it can complete a fresh security check. See the blocklist example.

Deployment notes

Updater state, backups, and the recovery journal are stored under HAVEN_DATA_DIR/extension-updates. Plugin and theme files remain in Haven's existing plugins/ and themes/ directories. Container deployments must keep these locations writable and persistent. Back up the extension directories and updater state together.

Run one Haven process against these directories. The updater serializes work inside one process but does not coordinate multiple Haven processes sharing the same storage.

Update offers belong to the administrator who checked and expire after ten minutes. A new check or successful replacement invalidates earlier offers. Local edits and source changes are reported as conflicts and are never overwritten automatically.

Downloads use HTTPS and approved public GitHub hosts without credentials. Requests have time and size limits. Haven caps the blocklist at 1 MiB, GitHub JSON responses at 2 MiB, and extension assets at 5 MiB. It checks at most five pages of releases and 30 manifests per repository. An incomplete check is reported as an error rather than “up to date.”