Versioning consistency and bump both the XPI and backend to version 0.2.0
Build LinkLog Development Image / development-image (push) Successful in 11s
Release LinkLog / release (push) Failing after 2s

This commit is contained in:
Olaf
2026-08-28 10:38:27 +02:00
parent 5696c89dee
commit 9bf9c94dd6
16 changed files with 241 additions and 21 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
id: release id: release
run: | run: |
python3 scripts/release/validate_release.py --github-output "$GITHUB_OUTPUT" 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 if [ "${GITHUB_REF_NAME#v}" != "$backend_version" ]; then
echo "tag ${GITHUB_REF_NAME} does not match backend version $backend_version" >&2 echo "tag ${GITHUB_REF_NAME} does not match backend version $backend_version" >&2
exit 1 exit 1
+1
View File
@@ -22,6 +22,7 @@
* Added a "Re-fetch Title" button to manually re-scrape the title after editing the URL * 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 * 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 * 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
* Fixed element ID conflict on the Admin page so Available Themes load correctly * Fixed element ID conflict on the Admin page so Available Themes load correctly
+5 -1
View File
@@ -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_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)) EXTENSION_SOURCES := $(addprefix webextension/,$(EXTENSION_FILES))
XPI_VALIDATOR := scripts/release/validate_xpi.py 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 all: logos
@@ -28,6 +29,9 @@ logos: check-tools $(GENERATED_LOGOS)
xpi: $(XPI_OUTPUT) xpi: $(XPI_OUTPUT)
update-updates: $(UPDATES_GENERATOR) webextension/manifest.json
@python3 $(UPDATES_GENERATOR)
$(XPI_OUTPUT): $(EXTENSION_SOURCES) $(GENERATED_LOGOS) $(XPI_VALIDATOR) $(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; } @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) @mkdir -p $(XPI_UNSIGNED_DIR) $(XPI_SIGNED_DIR)
+4 -4
View File
@@ -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. 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. 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). LinkLog is licensed under the GNU General Public License, version 3 or any later version. See [LICENSE](LICENSE).
## Local Installation ## Local Installation
@@ -110,11 +110,11 @@ This publishes `${APP_PORT:-8000}` and defaults the application URL to `http://l
## Releases ## 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-<version>.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-<version>.xpi` checked into the repository.
The signed XPI is produced manually and should be checked into `XPI/signed/LinkLog-<version>.xpi`. The workflow validates the latest signed XPI's embedded manifest, publishes Docker images to `git.kolkman.org/olaf/link-log:<backend-version>` 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-<version>.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:<backend-version>` 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-<version>.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-<version>.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. 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.
+24
View File
@@ -1466,3 +1466,27 @@ Make sure the webplugin has the same functionality as just implemented in the ne
### Assistant outcome ### 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 `<br>` 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. 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 `<br>` 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:<hex>` 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.
+4
View File
@@ -263,6 +263,10 @@
241. In the check duplicate I have a `<br>` tag, but that shows as formatted text on the page, I want a real break to occur at that position. 241. In the check duplicate I have a `<br>` 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. 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. 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 ## Future entries
Binary file not shown.
Binary file not shown.
+12 -2
View File
@@ -1,7 +1,8 @@
## Copyright © 2026 Olaf Kolkman ## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later ## SPDX-License-Identifier: GPL-3.0-or-later
from dataclasses import dataclass from dataclasses import dataclass, field
import json
import os import os
from pathlib import Path from pathlib import Path
@@ -10,6 +11,7 @@ from cryptography.fernet import Fernet
BASE_DIR = Path(__file__).resolve().parent.parent.parent BASE_DIR = Path(__file__).resolve().parent.parent.parent
DB_PATH = BASE_DIR / 'data' / 'linklog.db' DB_PATH = BASE_DIR / 'data' / 'linklog.db'
VERSION_FILE = BASE_DIR.parent / 'frontend' / 'version.json'
def normalize_public_url(value: str) -> str: def normalize_public_url(value: str) -> str:
@@ -20,11 +22,19 @@ def normalize_public_url(value: str) -> str:
return f'{scheme}://{value}' 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 @dataclass
class Settings: class Settings:
app_env: str = os.getenv('APP_ENV', 'development').lower() app_env: str = os.getenv('APP_ENV', 'development').lower()
app_name: str = os.getenv('LINKLOG_APP_NAME', 'LinkLog') 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}') 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') secret_key: str = os.getenv('LINKLOG_SECRET_KEY', 'dev-secret-key-change-me')
data_encryption_key: str = os.getenv('LINKLOG_DATA_ENCRYPTION_KEY', '') data_encryption_key: str = os.getenv('LINKLOG_DATA_ENCRYPTION_KEY', '')
+6 -2
View File
@@ -2,6 +2,7 @@
## SPDX-License-Identifier: GPL-3.0-or-later ## SPDX-License-Identifier: GPL-3.0-or-later
import json import json
from pathlib import Path
import threading import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs from urllib.parse import parse_qs
@@ -11,6 +12,7 @@ from unittest.mock import MagicMock, patch
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from backend.app.main import app 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.database import get_connection, hash_password
from backend.app.services.email_service import get_smtp_settings from backend.app.services.email_service import get_smtp_settings
from backend.app.services.login_throttle import clear_login_failures from backend.app.services.login_throttle import clear_login_failures
@@ -31,7 +33,7 @@ def login_headers(username='alice'):
def test_login_returns_token(): def test_login_returns_token():
assert app.version == '0.1.1' assert app.version == settings.version
response = client.post('/api/auth/login', json={ response = client.post('/api/auth/login', json={
'email': 'alice@example.com', 'email': 'alice@example.com',
'password': 'secret123', 'password': 'secret123',
@@ -806,7 +808,9 @@ def test_public_and_admin_pages_render_html():
assert about_page.status_code == 200 assert about_page.status_code == 200
assert 'Save the good stuff' in about_page.text assert 'Save the good stuff' in about_page.text
assert '<h2>Plugin</h2>' in about_page.text assert '<h2>Plugin</h2>' 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 'id="auth-about-link" href="/about"' in about_page.text
assert client.get('/admin').status_code == 200 assert client.get('/admin').status_code == 200
admin_page = client.get('/admin').text admin_page = client.get('/admin').text
+1 -1
View File
@@ -2,7 +2,7 @@
services: services:
app: 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} container_name: ${APP_CONTAINER_NAME:-linklog-app}
volumes: volumes:
- ./linklog_data:/app/backend/data - ./linklog_data:/app/backend/data
+1 -1
View File
@@ -60,7 +60,7 @@
<section class="link-item"> <section class="link-item">
<h2>Plugin</h2> <h2>Plugin</h2>
<p>Install the Firefox plugin to save links directly from your browser. <a <p>Install the Firefox plugin to save links directly from your browser. <a
href="https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi" href="https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.2.0.xpi"
download>Download and install the Plugin</a>.</p> download>Download and install the Plugin</a>.</p>
</section> </section>
</main> </main>
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "0.1.1" "version": "0.2.0"
} }
+100
View File
@@ -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()
+63 -6
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Validate the version and checked-in artifacts for a LinkLog release.""" """Validate the version and checked-in artifacts for a LinkLog release."""
import hashlib
import json import json
import re import re
import sys import sys
@@ -9,9 +10,12 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2] 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' SIGNED_DIR = ROOT / 'XPI' / 'signed'
MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json' 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+') 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])) 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: def main() -> None:
settings = SETTINGS_PATH.read_text() version_data = json.loads(VERSION_FILE.read_text())
match = re.search(r"version: str = os\.getenv\('LINKLOG_VERSION', '([^']+)'\)", settings) backend_version = version_data.get('version')
if not match: if not backend_version:
fail('backend version default could not be found') fail(f'version could not be found in {VERSION_FILE.relative_to(ROOT)}')
backend_version = match.group(1)
if not VERSION_RE.fullmatch(backend_version): if not VERSION_RE.fullmatch(backend_version):
fail(f'backend version {backend_version} is not a valid three-part 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: if archive.testzip() is not None:
fail('signed XPI contains a corrupt member') 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) signed_xpi = xpi_path.relative_to(ROOT)
if len(sys.argv) == 3 and sys.argv[1] == '--github-output': if len(sys.argv) == 3 and sys.argv[1] == '--github-output':
with Path(sys.argv[2]).open('a') as output: with Path(sys.argv[2]).open('a') as output:
+17 -1
View File
@@ -2,9 +2,25 @@
"addons": { "addons": {
"linklog@kolkman.org": { "linklog@kolkman.org": {
"updates": [ "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", "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"
}
}
} }
] ]
} }