From 9bf9c94dd68c56a5b2435b1099b9d3e71e21aa87 Mon Sep 17 00:00:00 2001 From: Olaf Date: Fri, 28 Aug 2026 10:38:27 +0200 Subject: [PATCH] Versioning consistency and bump both the XPI and backend to version 0.2.0 --- .gitea/workflows/release.yml | 2 +- CHANGELOG.MD | 1 + Makefile | 6 +- README.md | 8 +-- VIBE/CHAT_LOG.md | 24 +++++++ VIBE/PROMPTS.md | 4 ++ XPI/signed/LinkLog-0.2.0.xpi | Bin 0 -> 60305 bytes XPI/signed/LinkLog-dummy.xpi | Bin 24225 -> 0 bytes backend/app/core/config.py | 14 +++- backend/tests/test_api.py | 8 ++- docker-compose-example.yml | 2 +- frontend/templates/about.html | 2 +- frontend/version.json | 2 +- scripts/release/generate_updates.py | 100 ++++++++++++++++++++++++++++ scripts/release/validate_release.py | 69 +++++++++++++++++-- webextension/updates.json | 20 +++++- 16 files changed, 241 insertions(+), 21 deletions(-) create mode 100644 XPI/signed/LinkLog-0.2.0.xpi delete mode 100644 XPI/signed/LinkLog-dummy.xpi create mode 100644 scripts/release/generate_updates.py diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 955bd30..c5c34e6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -20,7 +20,7 @@ jobs: id: release run: | python3 scripts/release/validate_release.py --github-output "$GITHUB_OUTPUT" - backend_version=$(python3 -c "import re; text=open('backend/app/core/config.py').read(); print(re.search(r\"version: str = os\\.getenv\\('LINKLOG_VERSION', '([^']+)'\\)\", text).group(1))") + backend_version=$(python3 -c "import json; print(json.load(open('frontend/version.json'))['version'])") if [ "${GITHUB_REF_NAME#v}" != "$backend_version" ]; then echo "tag ${GITHUB_REF_NAME} does not match backend version $backend_version" >&2 exit 1 diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 100b296..b9d1eb9 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -22,6 +22,7 @@ * Added a "Re-fetch Title" button to manually re-scrape the title after editing the URL * New Entry duplicate detection and submission strip known tracking parameters (utm_*, gclid, fbclid, etc.) from URLs, mirroring the browser extension * Browser extension popup now matches the New Entry page: it warns on duplicate title/URL, notes when the stored link has a different URL, and gained a "Re-fetch title" button; bumped extension version to 0.2.0 + * Generate Firefox update metadata from every signed XPI with verified SHA-256 archive hashes ### Fixed * Fixed element ID conflict on the Admin page so Available Themes load correctly diff --git a/Makefile b/Makefile index d978709..8890642 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,9 @@ XPI_OUTPUT := $(XPI_UNSIGNED_DIR)/$(XPI_FILE) EXTENSION_FILES := manifest.json logo.svg icon-16.png icon-32.png icon-48.png icon-96.png options.css options.html options.js popup.css popup.html popup.js l10n.js _locales/en-US/messages.json _locales/es/messages.json _locales/de/messages.json _locales/fr/messages.json _locales/nl/messages.json EXTENSION_SOURCES := $(addprefix webextension/,$(EXTENSION_FILES)) XPI_VALIDATOR := scripts/release/validate_xpi.py +UPDATES_GENERATOR := scripts/release/generate_updates.py -.PHONY: all logos xpi check-tools clean-generated +.PHONY: all logos xpi update-updates check-tools clean-generated all: logos @@ -28,6 +29,9 @@ logos: check-tools $(GENERATED_LOGOS) xpi: $(XPI_OUTPUT) +update-updates: $(UPDATES_GENERATOR) webextension/manifest.json + @python3 $(UPDATES_GENERATOR) + $(XPI_OUTPUT): $(EXTENSION_SOURCES) $(GENERATED_LOGOS) $(XPI_VALIDATOR) @test -n "$(EXTENSION_VERSION)" || { echo "Error: extension version is missing from webextension/manifest.json" >&2; exit 1; } @mkdir -p $(XPI_UNSIGNED_DIR) $(XPI_SIGNED_DIR) diff --git a/README.md b/README.md index 0f77f89..d9e3be2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ For local development: The backend currently uses FastAPI, uvicorn, SQLite, and Pydantic. `httpx2` is included for the Starlette-compatible test client. Jinja2 is included for server-rendered HTML templates. -The backend version is `0.1.1` and is exposed through the FastAPI/OpenAPI metadata. It can be overridden with `LINKLOG_VERSION`. +The backend version is `0.2.0` and is exposed through the FastAPI/OpenAPI metadata. It is read from `frontend/version.json`, the single source of truth shared by the backend and frontend. LinkLog is licensed under the GNU General Public License, version 3 or any later version. See [LICENSE](LICENSE). ## Local Installation @@ -110,11 +110,11 @@ This publishes `${APP_PORT:-8000}` and defaults the application URL to `http://l ## Releases -Releases run in Gitea Actions when a `v*` tag is pushed. The Docker release version comes from `LINKLOG_VERSION`'s default in `backend/app/core/config.py`; the tag must match that backend version. The Firefox plugin version is independent and comes from the most recent signed `XPI/signed/LinkLog-.xpi` checked into the repository. +Releases run in Gitea Actions when a `v*` tag is pushed. The Docker release version comes from `frontend/version.json`; the tag must match that version. The Firefox plugin version is independent and comes from the most recent signed `XPI/signed/LinkLog-.xpi` checked into the repository. -The signed XPI is produced manually and should be checked into `XPI/signed/LinkLog-.xpi`. The workflow validates the latest signed XPI's embedded manifest, publishes Docker images to `git.kolkman.org/olaf/link-log:` and `:latest`, and creates a release README that describes the project, the current backend/container version, and the raw signed XPI download URL with the plugin version. +The signed XPI is produced manually and should be checked into `XPI/signed/LinkLog-.xpi`. Run `make update-updates` after adding a signed XPI to regenerate `webextension/updates.json` from every valid signed release artifact. The workflow validates the latest signed XPI's embedded manifest, publishes Docker images to `git.kolkman.org/olaf/link-log:` and `:latest`, and creates a release README that describes the project, the current backend/container version, and the raw signed XPI download URL with the plugin version. -The extension's `update_url` points at the stable raw repository URL `https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json`. Update `webextension/updates.json` with each signed XPI version and commit it together with the XPI. Release READMEs point to the raw signed XPI at `https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-.xpi`. +The extension's `update_url` points at the stable raw repository URL `https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json`. Commit the regenerated `webextension/updates.json` together with each signed XPI. Release READMEs point to the raw signed XPI at `https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-.xpi`. The workflow requires Gitea Actions secrets named `REGISTRY_USERNAME`, `REGISTRY_TOKEN`, and `RELEASE_TOKEN`. `REGISTRY_TOKEN` is a Gitea access token with permission to push packages; `RELEASE_TOKEN` needs permission to create releases and upload release assets. diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index 898b8e3..229a03a 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -1466,3 +1466,27 @@ Make sure the webplugin has the same functionality as just implemented in the ne ### Assistant outcome Brought `webextension/popup.js` to parity with `new-entry.js`: the duplicate check now distinguishes an exact title/URL match from a title match with a differing stored URL (using the same `/api/links/check` `url_matches`/`stored_url` fields) and shows an additional localized note in the latter case; added a "Re-fetch title" button that calls the backend `/api/scrape` endpoint (guarded by existing backend-permission checks) and auto-fills the title on URL blur when empty; reworked `setStatus` to render real `
` line breaks via DOM APIs instead of `textContent`. Added `duplicateLinkDifferentUrl`, `refetchTitle`, and `titleFetchFailed` strings to all five locale files, updated `popup.html` with the new button, and bumped `manifest.json` to version `0.2.0`. Full backend test suite (36 tests) passed. + +### User +Add a feature to the Make file that updates the updates.json file based on available signed releases. + +### Assistant outcome +Added `make update-updates`, which regenerates `webextension/updates.json` from valid versioned signed XPIs. The generator reads each packaged manifest, validates its version, add-on ID, and Firefox minimum version, and ignores non-release artifacts. + +### User +Add sha hashes to the updates.json + +### Assistant outcome +Added Firefox-compatible `sha256:` update hashes generated from each signed XPI's bytes. Release validation now rejects missing or mismatched hashes; the generated metadata and release validator passed for signed versions 0.2.0 and 0.1.0. + +### User +Update changelog and VIBE + +### Assistant outcome +Recorded the signed-XPI update metadata automation and SHA-256 integrity hashes in the v0.2.0 changelog and VIBE logs. + +### User +Make sure that when the plugin is signed the version in the link it the about page is updated too + +### Assistant outcome +Extended `make update-updates` to set the About page plugin download URL to the newest valid signed XPI. Release validation now also rejects an About page whose link does not match the latest signed artifact; verified with LinkLog 0.2.0. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index 0508614..0128c39 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -263,6 +263,10 @@ 241. In the check duplicate I have a `
` tag, but that shows as formatted text on the page, I want a real break to occur at that position. 242. Update VIBE and Changelog. 243. Make sure the webplugin has the same functionality as just implemented in the new-entry page. Increase its version number to 0.2.0. +244. Add a feature to the Make file that updates the updates.json file based on available signed releases. +245. Add sha hashes to the updates.json +246. Update changelog and VIBE +247. Make sure that when the plugin is signed the version in the link it the about page is updated too ## Future entries diff --git a/XPI/signed/LinkLog-0.2.0.xpi b/XPI/signed/LinkLog-0.2.0.xpi new file mode 100644 index 0000000000000000000000000000000000000000..f4fcd8d08961ff20ddab6e8ec79faa91c5d928d1 GIT binary patch literal 60305 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 I{Cf9)0BM9A8vphmL`J@Q#afZi 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 diff --git a/backend/app/core/config.py b/backend/app/core/config.py index cf3c0e5..9cb165a 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,7 +1,8 @@ ## Copyright © 2026 Olaf Kolkman ## SPDX-License-Identifier: GPL-3.0-or-later -from dataclasses import dataclass +from dataclasses import dataclass, field +import json import os from pathlib import Path @@ -10,6 +11,7 @@ from cryptography.fernet import Fernet BASE_DIR = Path(__file__).resolve().parent.parent.parent DB_PATH = BASE_DIR / 'data' / 'linklog.db' +VERSION_FILE = BASE_DIR.parent / 'frontend' / 'version.json' def normalize_public_url(value: str) -> str: @@ -20,11 +22,19 @@ def normalize_public_url(value: str) -> str: return f'{scheme}://{value}' +def load_version() -> str: + # frontend/version.json is the single source of truth for the app version, shared by backend and frontend. + try: + return json.loads(VERSION_FILE.read_text())['version'] + except (OSError, KeyError, ValueError): + return '0.0.0' + + @dataclass class Settings: app_env: str = os.getenv('APP_ENV', 'development').lower() app_name: str = os.getenv('LINKLOG_APP_NAME', 'LinkLog') - version: str = os.getenv('LINKLOG_VERSION', '0.1.1') + version: str = field(default_factory=load_version) database_url: str = os.getenv('LINKLOG_DATABASE_URL', f'sqlite:///{DB_PATH}') secret_key: str = os.getenv('LINKLOG_SECRET_KEY', 'dev-secret-key-change-me') data_encryption_key: str = os.getenv('LINKLOG_DATA_ENCRYPTION_KEY', '') diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 751628c..b1664f2 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -2,6 +2,7 @@ ## SPDX-License-Identifier: GPL-3.0-or-later import json +from pathlib import Path import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import parse_qs @@ -11,6 +12,7 @@ from unittest.mock import MagicMock, patch from fastapi.testclient import TestClient from backend.app.main import app +from backend.app.core.config import settings from backend.app.database import get_connection, hash_password from backend.app.services.email_service import get_smtp_settings from backend.app.services.login_throttle import clear_login_failures @@ -31,7 +33,7 @@ def login_headers(username='alice'): def test_login_returns_token(): - assert app.version == '0.1.1' + assert app.version == settings.version response = client.post('/api/auth/login', json={ 'email': 'alice@example.com', 'password': 'secret123', @@ -806,7 +808,9 @@ def test_public_and_admin_pages_render_html(): assert about_page.status_code == 200 assert 'Save the good stuff' in about_page.text assert '

Plugin

' in about_page.text - assert 'https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi' in about_page.text + updates = json.loads((Path(__file__).resolve().parents[2] / 'webextension' / 'updates.json').read_text()) + latest_update = updates['addons']['linklog@kolkman.org']['updates'][0] + assert latest_update['update_link'] in about_page.text assert 'id="auth-about-link" href="/about"' in about_page.text assert client.get('/admin').status_code == 200 admin_page = client.get('/admin').text diff --git a/docker-compose-example.yml b/docker-compose-example.yml index 5fb52e6..951f25c 100644 --- a/docker-compose-example.yml +++ b/docker-compose-example.yml @@ -2,7 +2,7 @@ services: app: - image: git.kolkman.org/olaf/link-log:development # or :latest or a version-tag + image: git.kolkman.org/olaf/link-log:${LINKLOG_VERSION:-latest} # or :development or a :version-tag container_name: ${APP_CONTAINER_NAME:-linklog-app} volumes: - ./linklog_data:/app/backend/data diff --git a/frontend/templates/about.html b/frontend/templates/about.html index f0f8f2c..53769ca 100644 --- a/frontend/templates/about.html +++ b/frontend/templates/about.html @@ -60,7 +60,7 @@ diff --git a/frontend/version.json b/frontend/version.json index cf0d650..d787358 100644 --- a/frontend/version.json +++ b/frontend/version.json @@ -1,3 +1,3 @@ { - "version": "0.1.1" + "version": "0.2.0" } \ No newline at end of file diff --git a/scripts/release/generate_updates.py b/scripts/release/generate_updates.py new file mode 100644 index 0000000..33071f9 --- /dev/null +++ b/scripts/release/generate_updates.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Generate Firefox update metadata from LinkLog signed XPI artifacts.""" + +import hashlib +import json +import re +import sys +import zipfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +SIGNED_DIR = ROOT / 'XPI' / 'signed' +MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json' +UPDATES_PATH = ROOT / 'webextension' / 'updates.json' +ABOUT_TEMPLATE_PATH = ROOT / 'frontend' / 'templates' / 'about.html' +RAW_BASE_URL = 'https://git.kolkman.org/olaf/Link-Log/raw/branch/main' +XPI_NAME_RE = re.compile(r'LinkLog-(\d+\.\d+\.\d+)\.xpi') +ABOUT_XPI_URL_RE = re.compile(rf'{re.escape(RAW_BASE_URL)}/XPI/signed/LinkLog-\d+\.\d+\.\d+\.xpi') + + +def fail(message: str) -> None: + raise SystemExit(f'update metadata generation failed: {message}') + + +def version_key(version: str) -> tuple[int, int, int]: + return tuple(int(part) for part in version.split('.')) + + +def read_packaged_manifest(xpi_path: Path) -> dict: + try: + with zipfile.ZipFile(xpi_path) as archive: + if archive.testzip() is not None: + fail(f'{xpi_path.relative_to(ROOT)} contains a corrupt member') + return json.loads(archive.read('manifest.json')) + except (OSError, KeyError, json.JSONDecodeError, zipfile.BadZipFile) as error: + fail(f'could not read {xpi_path.relative_to(ROOT)}: {error}') + + +def sha256_digest(xpi_path: Path) -> str: + with xpi_path.open('rb') as xpi_file: + return hashlib.file_digest(xpi_file, 'sha256').hexdigest() + + +def update_about_plugin_link(xpi_path: Path) -> None: + about_template = ABOUT_TEMPLATE_PATH.read_text() + latest_xpi_url = f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}' + updated_template, replacements = ABOUT_XPI_URL_RE.subn(latest_xpi_url, about_template) + if replacements != 1: + fail(f'expected one signed XPI link in {ABOUT_TEMPLATE_PATH.relative_to(ROOT)}; found {replacements}') + ABOUT_TEMPLATE_PATH.write_text(updated_template) + + +def main() -> None: + source_manifest = json.loads(MANIFEST_PATH.read_text()) + addon_id = source_manifest.get('browser_specific_settings', {}).get('gecko', {}).get('id') + if not addon_id: + fail('webextension/manifest.json is missing browser_specific_settings.gecko.id') + + releases = [] + for xpi_path in SIGNED_DIR.glob('LinkLog-*.xpi'): + match = XPI_NAME_RE.fullmatch(xpi_path.name) + if not match: + continue + version = match.group(1) + manifest = read_packaged_manifest(xpi_path) + if manifest.get('version') != version: + fail(f'{xpi_path.relative_to(ROOT)} manifest version does not match its filename') + gecko = manifest.get('browser_specific_settings', {}).get('gecko', {}) + if gecko.get('id') != addon_id: + fail(f'{xpi_path.relative_to(ROOT)} add-on id does not match webextension/manifest.json') + strict_min_version = gecko.get('strict_min_version') + if not strict_min_version: + fail(f'{xpi_path.relative_to(ROOT)} is missing browser_specific_settings.gecko.strict_min_version') + releases.append((version, xpi_path, strict_min_version, sha256_digest(xpi_path))) + + if not releases: + fail(f'no signed LinkLog release artifacts found in {SIGNED_DIR.relative_to(ROOT)}') + + releases.sort(key=lambda release: version_key(release[0]), reverse=True) + updates = [ + { + 'version': version, + 'update_link': f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}', + 'update_hash': f'sha256:{digest}', + 'applications': { + 'gecko': { + 'strict_min_version': strict_min_version, + }, + }, + } + for version, xpi_path, strict_min_version, digest in releases + ] + UPDATES_PATH.write_text(json.dumps({'addons': {addon_id: {'updates': updates}}}, indent=2) + '\n') + update_about_plugin_link(releases[0][1]) + print(f'updated {UPDATES_PATH.relative_to(ROOT)} with {len(updates)} signed release(s)') + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/scripts/release/validate_release.py b/scripts/release/validate_release.py index f8c6b86..fcd4ef1 100644 --- a/scripts/release/validate_release.py +++ b/scripts/release/validate_release.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Validate the version and checked-in artifacts for a LinkLog release.""" +import hashlib import json import re import sys @@ -9,9 +10,12 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] -SETTINGS_PATH = ROOT / 'backend' / 'app' / 'core' / 'config.py' +VERSION_FILE = ROOT / 'frontend' / 'version.json' SIGNED_DIR = ROOT / 'XPI' / 'signed' MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json' +UPDATES_PATH = ROOT / 'webextension' / 'updates.json' +ABOUT_TEMPLATE_PATH = ROOT / 'frontend' / 'templates' / 'about.html' +RAW_BASE_URL = 'https://git.kolkman.org/olaf/Link-Log/raw/branch/main' VERSION_RE = re.compile(r'\d+\.\d+\.\d+') @@ -34,12 +38,62 @@ def find_latest_signed_xpi() -> tuple[str, Path]: return max(candidates, key=lambda candidate: version_key(candidate[0])) +def validate_self_update(source_manifest: dict, extension_version: str, xpi_path: Path) -> None: + gecko_settings = source_manifest.get('browser_specific_settings', {}).get('gecko', {}) + addon_id = gecko_settings.get('id') + strict_min_version = gecko_settings.get('strict_min_version') + if not addon_id: + fail('webextension/manifest.json is missing browser_specific_settings.gecko.id') + + updates_data = json.loads(UPDATES_PATH.read_text()) + addon_entry = updates_data.get('addons', {}).get(addon_id) + if not addon_entry: + fail(f'{UPDATES_PATH.relative_to(ROOT)} has no entry for add-on id {addon_id!r}') + + entry = next((u for u in addon_entry.get('updates', []) if u.get('version') == extension_version), None) + if entry is None: + fail( + f'{UPDATES_PATH.relative_to(ROOT)} has no update entry for version {extension_version!r}; ' + 'add one alongside the signed XPI so the self-update mechanism can find it' + ) + + expected_link = f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}' + if entry.get('update_link') != expected_link: + fail( + f'{UPDATES_PATH.relative_to(ROOT)} update_link {entry.get("update_link")!r} does not match ' + f'the expected raw signed XPI URL {expected_link!r}' + ) + + with xpi_path.open('rb') as xpi_file: + expected_hash = f'sha256:{hashlib.file_digest(xpi_file, "sha256").hexdigest()}' + if entry.get('update_hash') != expected_hash: + fail( + f'{UPDATES_PATH.relative_to(ROOT)} update_hash {entry.get("update_hash")!r} does not match ' + f'the SHA-256 hash of {xpi_path.relative_to(ROOT)}' + ) + + entry_min_version = entry.get('applications', {}).get('gecko', {}).get('strict_min_version') + if entry_min_version != strict_min_version: + fail( + f'{UPDATES_PATH.relative_to(ROOT)} applications.gecko.strict_min_version {entry_min_version!r} ' + f'does not match webextension/manifest.json strict_min_version {strict_min_version!r}' + ) + + +def validate_about_plugin_link(xpi_path: Path) -> None: + expected_link = f'{RAW_BASE_URL}/{xpi_path.relative_to(ROOT).as_posix()}' + if expected_link not in ABOUT_TEMPLATE_PATH.read_text(): + fail( + f'{ABOUT_TEMPLATE_PATH.relative_to(ROOT)} does not link to the latest signed XPI ' + f'{xpi_path.relative_to(ROOT)}' + ) + + def main() -> None: - settings = SETTINGS_PATH.read_text() - match = re.search(r"version: str = os\.getenv\('LINKLOG_VERSION', '([^']+)'\)", settings) - if not match: - fail('backend version default could not be found') - backend_version = match.group(1) + version_data = json.loads(VERSION_FILE.read_text()) + backend_version = version_data.get('version') + if not backend_version: + fail(f'version could not be found in {VERSION_FILE.relative_to(ROOT)}') if not VERSION_RE.fullmatch(backend_version): fail(f'backend version {backend_version} is not a valid three-part version') @@ -64,6 +118,9 @@ def main() -> None: if archive.testzip() is not None: fail('signed XPI contains a corrupt member') + validate_self_update(source_manifest, extension_version, xpi_path) + validate_about_plugin_link(xpi_path) + signed_xpi = xpi_path.relative_to(ROOT) if len(sys.argv) == 3 and sys.argv[1] == '--github-output': with Path(sys.argv[2]).open('a') as output: diff --git a/webextension/updates.json b/webextension/updates.json index bb810d9..d37403c 100644 --- a/webextension/updates.json +++ b/webextension/updates.json @@ -2,11 +2,27 @@ "addons": { "linklog@kolkman.org": { "updates": [ + { + "version": "0.2.0", + "update_link": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.2.0.xpi", + "update_hash": "sha256:86619ec9aa35345c5c2bcad8fa5701762b8bd237ad9457b131404ff68bc2ce6b", + "applications": { + "gecko": { + "strict_min_version": "142.0" + } + } + }, { "version": "0.1.0", - "update_link": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi" + "update_link": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi", + "update_hash": "sha256:d95e23339facfa2a499bb35f9130d41739f622a3c0ac197ac3fd8cb76d6d6110", + "applications": { + "gecko": { + "strict_min_version": "142.0" + } + } } ] } } -} \ No newline at end of file +}