Compare commits
6
Commits
5dbef8f23f
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c11b25c20a | ||
|
|
c27aad58ae | ||
|
|
7dffaad8e5 | ||
|
|
583026418d | ||
|
|
fa6d88a768 | ||
|
|
b6c01878a8 |
@@ -19,11 +19,10 @@ jobs:
|
||||
- name: Validate versions and signed XPI
|
||||
id: release
|
||||
run: |
|
||||
python3 scripts/release/validate_release.py
|
||||
version=$(python3 -c "import json; print(json.load(open('frontend/version.json'))['version'])")
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
if [ "${GITHUB_REF_NAME#v}" != "$version" ]; then
|
||||
echo "tag ${GITHUB_REF_NAME} does not match release version $version" >&2
|
||||
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))")
|
||||
if [ "${GITHUB_REF_NAME#v}" != "$backend_version" ]; then
|
||||
echo "tag ${GITHUB_REF_NAME} does not match backend version $backend_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -40,51 +39,113 @@ jobs:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.release.outputs.version }}
|
||||
${{ env.IMAGE_NAME }}:${{ steps.release.outputs.backend_version }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
labels: |
|
||||
org.opencontainers.image.version=${{ steps.release.outputs.version }}
|
||||
org.opencontainers.image.version=${{ steps.release.outputs.backend_version }}
|
||||
org.opencontainers.image.source=https://git.kolkman.org/olaf/Link-Log
|
||||
|
||||
- name: Generate release README
|
||||
env:
|
||||
BACKEND_VERSION: ${{ steps.release.outputs.backend_version }}
|
||||
PLUGIN_VERSION: ${{ steps.release.outputs.plugin_version }}
|
||||
SIGNED_XPI: ${{ steps.release.outputs.signed_xpi }}
|
||||
run: |
|
||||
cat > release-readme.md <<EOF
|
||||
# LinkLog $BACKEND_VERSION
|
||||
|
||||
LinkLog is a Firefox extension and Python web service for saving links with a title, comment, timestamp, and tracking parameters removed. The service stores links in SQLite and can publish them through plugins, including Mastodon.
|
||||
|
||||
## Docker Container
|
||||
|
||||
The current backend/container version is $BACKEND_VERSION. Pull it from the Gitea container registry:
|
||||
|
||||
\`\`\`sh
|
||||
docker pull $IMAGE_NAME:$BACKEND_VERSION
|
||||
\`\`\`
|
||||
|
||||
The same image is also published as:
|
||||
|
||||
\`\`\`sh
|
||||
docker pull $IMAGE_NAME:latest
|
||||
\`\`\`
|
||||
The developer version of the backend is always published as $IMAGE_NAME:latest, which may be ahead of the current release version and may be unstable.
|
||||
Additional information about the backend can be found in the [README](https://git.kolkman.org/olaf/Link-Log/src/branch/main/backend/README.md).
|
||||
|
||||
## Firefox Extension
|
||||
|
||||
The current signed Firefox plugin version, compatible with this version of the backend, is $PLUGIN_VERSION. Download it from the raw repository artifact:
|
||||
|
||||
https://git.kolkman.org/olaf/Link-Log/raw/branch/main/$SIGNED_XPI
|
||||
EOF
|
||||
|
||||
- name: Create Gitea release
|
||||
id: gitea_release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
VERSION: ${{ steps.release.outputs.backend_version }}
|
||||
run: |
|
||||
response=$(curl --fail-with-body --silent --show-error \
|
||||
payload_file=$(mktemp)
|
||||
python3 - <<'PY' > "$payload_file"
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
version = os.environ['VERSION']
|
||||
print(json.dumps({
|
||||
'tag_name': f'v{version}',
|
||||
'name': f'LinkLog {version}',
|
||||
'body': Path('release-readme.md').read_text(),
|
||||
'draft': False,
|
||||
'prerelease': False,
|
||||
}))
|
||||
PY
|
||||
response_file=$(mktemp)
|
||||
response_status=$(curl --silent --show-error -o "$response_file" -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"v$VERSION\",\"name\":\"LinkLog $VERSION\",\"draft\":false,\"prerelease\":false}" \
|
||||
--data-binary "@$payload_file" \
|
||||
https://git.kolkman.org/api/v1/repos/olaf/Link-Log/releases)
|
||||
release_id=$(printf '%s' "$response" | jq -r '.id')
|
||||
rm -f "$payload_file"
|
||||
if [ "$response_status" = 409 ]; then
|
||||
response_status=$(curl --silent --show-error -o "$response_file" -w '%{http_code}' \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
"https://git.kolkman.org/api/v1/repos/olaf/Link-Log/releases/tags/v$VERSION")
|
||||
fi
|
||||
if [ "$response_status" -lt 200 ] || [ "$response_status" -ge 300 ]; then
|
||||
cat "$response_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
response=$(cat "$response_file")
|
||||
rm -f "$response_file"
|
||||
release_id=$(printf '%s' "$response" | python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])')
|
||||
test "$release_id" != null
|
||||
test "$release_id" != 0
|
||||
upload_url="https://git.kolkman.org/api/v1/repos/olaf/Link-Log/releases/$release_id/assets"
|
||||
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload signed XPI and update manifest
|
||||
- name: Upload release README
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
UPLOAD_URL: ${{ steps.gitea_release.outputs.upload_url }}
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
run: |
|
||||
curl --fail-with-body --silent --show-error \
|
||||
response_status=$(curl --silent --show-error -o /tmp/linklog-readme-upload-response -w '%{http_code}' \
|
||||
-X POST -H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H 'Content-Type: application/x-xpinstall' \
|
||||
--data-binary "@XPI/signed/LinkLog-$VERSION.xpi" \
|
||||
"$UPLOAD_URL?name=LinkLog-$VERSION.xpi"
|
||||
curl --fail-with-body --silent --show-error \
|
||||
-X POST -H "Authorization: token $RELEASE_TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-binary @webextension/updates.json \
|
||||
"$UPLOAD_URL?name=updates.json"
|
||||
-H 'Content-Type: text/markdown' \
|
||||
--data-binary @release-readme.md \
|
||||
"$UPLOAD_URL?name=README.md")
|
||||
if [ "$response_status" -lt 200 ] || [ "$response_status" -ge 300 ]; then
|
||||
cat /tmp/linklog-readme-upload-response >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish release links
|
||||
env:
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
BACKEND_VERSION: ${{ steps.release.outputs.backend_version }}
|
||||
PLUGIN_VERSION: ${{ steps.release.outputs.plugin_version }}
|
||||
SIGNED_XPI: ${{ steps.release.outputs.signed_xpi }}
|
||||
run: |
|
||||
echo "Docker image: $IMAGE_NAME:$VERSION"
|
||||
echo "Signed XPI: https://git.kolkman.org/olaf/Link-Log/releases/download/v$VERSION/LinkLog-$VERSION.xpi"
|
||||
echo "Firefox update manifest: https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json"
|
||||
echo "Docker image: $IMAGE_NAME:$BACKEND_VERSION"
|
||||
echo "Signed XPI: https://git.kolkman.org/olaf/Link-Log/raw/branch/main/$SIGNED_XPI (version $PLUGIN_VERSION)"
|
||||
echo "Release README: README.md"
|
||||
|
||||
@@ -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 `frontend/version.json`; the Firefox plugin version comes from `webextension/manifest.json`. CI also requires both to match `LINKLOG_VERSION`'s default in `backend/app/core/config.py`.
|
||||
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.
|
||||
|
||||
The signed XPI is produced manually and must be checked into `XPI/signed/LinkLog-<version>.xpi` before creating the tag. The workflow validates the embedded manifest, publishes the XPI and `webextension/updates.json` as Gitea release assets, and publishes Docker images to `git.kolkman.org/olaf/link-log:<version>` and `:latest`.
|
||||
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 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. The release page provides a direct install link at `https://git.kolkman.org/olaf/Link-Log/releases/download/v<version>/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`. 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 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.
|
||||
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# Chat Log
|
||||
|
||||
### User
|
||||
The release server fails because its curl does not support `--fail-with-body`.
|
||||
|
||||
### Assistant outcome
|
||||
Replaced all three `--fail-with-body` uses in the release workflow with portable curl response capture and explicit HTTP status checks. Failed API and asset-upload responses now print their response body before exiting, while older curl versions remain supported.
|
||||
|
||||
### User
|
||||
When posting to Mastodon change the formatting so there is an empty line between “From my #LinkLog:” and the title.
|
||||
|
||||
### Assistant outcome
|
||||
Updated the Mastodon formatter so the prefix and title are separate paragraphs: `From my #LinkLog:` followed by a blank line and then the title. Existing comment, source URL, and tag paragraph formatting remains unchanged.
|
||||
|
||||
### User
|
||||
Check the modified pages for the correct link because a version number was added to the signed XPI.
|
||||
|
||||
### Assistant outcome
|
||||
Verified that the signed artifact is `XPI/signed/LinkLog-0.1.0.xpi` and that the About page, Profile page, `webextension/updates.json`, README, and related regression test all reference the matching versioned filename. No changes were required.
|
||||
|
||||
### User
|
||||
In the Profile page below the Save profile button add “If you have not downloaded the plugin yet find it here” with a link to the signed plugin.
|
||||
|
||||
### Assistant outcome
|
||||
Added the plugin download prompt immediately below the Save profile button, linking “find it here” to the signed `LinkLog-0.1.0.xpi` artifact. Added profile-page regression assertions and recorded the change in VIBE.
|
||||
|
||||
### User
|
||||
Add a Plugin section to the About page with a download link to the signed XPI file.
|
||||
|
||||
### Assistant outcome
|
||||
Added a Plugin section to the About page with a download link for the signed `LinkLog-0.1.0.xpi` artifact hosted on the repository's raw main branch. Added a page-rendering regression assertion.
|
||||
|
||||
### User
|
||||
The Firefox plugin tests report that `strict_min_version` 112 is too low for `data_collection_permissions`, `storage.session`, and `permissions.request`.
|
||||
|
||||
@@ -1154,3 +1184,21 @@ Continue to document every prompt and chat in the VIBE directory.
|
||||
|
||||
### Assistant outcome
|
||||
Recorded this prompt and its outcome in `VIBE/PROMPTS.md` and `VIBE/CHAT_LOG.md`. Future visible prompts and chat outcomes will continue to be appended there.
|
||||
|
||||
### User
|
||||
The release action should work differently. Rather than publishing the signed XPI and `updates.json`, it should produce a README describing the project, the latest Docker container version, and the latest signed XPI download URL with version number.
|
||||
|
||||
### Assistant outcome
|
||||
Changed the Gitea release workflow to generate `release-readme.md`, use it as the release body, and upload `README.md` as the release asset instead of publishing the signed XPI and `updates.json`. The README describes LinkLog, the Docker image pull location, and the raw signed XPI download URL.
|
||||
|
||||
### User
|
||||
The tagged version will be the version of the backend. However, the version of the plugin is set manually, just use the most recent signed plugin version that lives in the signed repo.
|
||||
|
||||
### Assistant outcome
|
||||
Decoupled backend and plugin release versions. The release tag and Docker image now use the backend `LINKLOG_VERSION`; the workflow discovers the newest semver signed artifact in `XPI/signed/`, validates its embedded manifest, and writes the plugin version and raw XPI URL into the generated release README. Updated release documentation and validated the script, workflow run blocks, and generated README output.
|
||||
|
||||
### User
|
||||
Update the VIBE directory with what you have done.
|
||||
|
||||
### Assistant outcome
|
||||
Appended the release workflow changes and this VIBE update request to `VIBE/PROMPTS.md` and `VIBE/CHAT_LOG.md` in the existing append-only format.
|
||||
|
||||
@@ -204,6 +204,14 @@
|
||||
197. Implement SA-002: replace raw infrastructure errors with redacted server-side logging, request IDs, and stable public reference messages.
|
||||
198. Implement SA-005: reject missing/default/weak production secrets at startup and validate the Fernet encryption key, with configuration tests.
|
||||
199. Fix Firefox manifest compatibility warnings by aligning the minimum version with data collection permissions and session storage support.
|
||||
200. Add a Plugin section to the About page with a download link to the signed XPI file.
|
||||
201. Below the Save profile button, add a link to download the signed plugin if it has not been downloaded yet.
|
||||
202. Check the modified pages for the correct versioned signed XPI link.
|
||||
203. When posting to Mastodon, add an empty line between "From my #LinkLog:" and the title.
|
||||
204. Fix the release workflow because the runner's curl does not support `--fail-with-body`.
|
||||
205. The release action should work differently. Rather than publishing the signed XPI and `updates.json`, it should produce a README describing the project, the latest Docker container version, and the latest signed XPI download URL with version number.
|
||||
206. The tagged version will be the version of the backend. However, the version of the plugin is set manually, just use the most recent signed plugin version that lives in the signed repo.
|
||||
207. Update the VIBE directory with what you have done.
|
||||
|
||||
## Future entries
|
||||
|
||||
|
||||
Binary file not shown.
@@ -46,6 +46,7 @@ app.include_router(user_config_router, prefix='/api/user')
|
||||
app.include_router(setup_router, prefix='/api/setup')
|
||||
|
||||
templates = Jinja2Templates(directory='frontend/templates')
|
||||
templates.env.globals['app_version'] = settings.version
|
||||
|
||||
|
||||
@app.get('/', response_class=HTMLResponse)
|
||||
|
||||
@@ -68,7 +68,9 @@ class MastodonPlugin(BasePlugin):
|
||||
post_prefix = f'#{str(config["hashtag"]).strip().lstrip("#")} '
|
||||
post_prefix = str(post_prefix if post_prefix is not None else DEFAULT_POST_PREFIX).strip()
|
||||
title = str(event.get('title') or '').strip()
|
||||
status_parts = [f'{post_prefix} {title}'.strip()]
|
||||
status_parts = [post_prefix.strip()]
|
||||
if title:
|
||||
status_parts.append(title)
|
||||
if event.get('comment'):
|
||||
status_parts.append(event['comment'])
|
||||
if title:
|
||||
|
||||
@@ -727,6 +727,8 @@ def test_public_and_admin_pages_render_html():
|
||||
about_page = client.get('/about')
|
||||
assert about_page.status_code == 200
|
||||
assert 'Save the good stuff' 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
|
||||
assert 'id="auth-about-link" href="/about"' in about_page.text
|
||||
assert client.get('/admin').status_code == 200
|
||||
admin_page = client.get('/admin').text
|
||||
@@ -794,7 +796,7 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
|
||||
assert received['path'] == '/api/v1/statuses'
|
||||
assert received['authorization'] == 'Bearer test-token'
|
||||
assert received['content_type'] == 'application/x-www-form-urlencoded'
|
||||
assert received['body'] == {'status': ['From my #LinkLog: A useful page\n\nWorth sharing\n\nfrom: https://example.com/useful\n\n#python #web']}
|
||||
assert received['body'] == {'status': ['From my #LinkLog:\n\nA useful page\n\nWorth sharing\n\nfrom: https://example.com/useful\n\n#python #web']}
|
||||
posted_item = next(item for item in client.get('/api/public/feed/alice', headers=headers).json() if item['id'] == response.json()['id'])
|
||||
assert posted_item['mastodon_posted'] is True
|
||||
finally:
|
||||
|
||||
@@ -51,6 +51,8 @@ def test_user_config_api_and_profile_page():
|
||||
assert 'id="auth-avatar"' not in page_response.text
|
||||
assert 'name="new_password_confirmation"' in page_response.text
|
||||
assert 'id="additional-email-form"' in page_response.text
|
||||
assert 'If you have not downloaded the plugin yet' in page_response.text
|
||||
assert 'https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi' in page_response.text
|
||||
|
||||
bob_login = client.post('/api/auth/login', json={
|
||||
'email': 'bob@example.com',
|
||||
|
||||
@@ -52,8 +52,14 @@
|
||||
<p>LinkLog is open source software. You can run your own instance, or contribute to the project on
|
||||
<a href="https://git.kolkman.org/olaf/Link-Log">my repository</a>.</p>
|
||||
</section>
|
||||
<section class="link-item">
|
||||
<h2>Plugin</h2>
|
||||
<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"
|
||||
download>Download and install the Plugin</a>.</p>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=3"></script>
|
||||
<script src="/static/theme.js?v=1"></script>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=2"></script>
|
||||
<script src="/static/theme.js?v=1"></script>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
{% endif %}
|
||||
<section id="feed" class="feed" aria-live="polite"></section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=3"></script>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<div id="label-list" class="plugin-list"></div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=3"></script>
|
||||
<script src="/static/theme.js?v=1"></script>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=3"></script>
|
||||
<script src="/static/theme.js?v=1"></script>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman</footer>
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman</footer>
|
||||
<script src="/static/theme.js?v=1"></script>
|
||||
<script src="/static/setup.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
</label>
|
||||
<img id="avatar-preview" class="profile-avatar-preview hidden" alt="Avatar preview" />
|
||||
<button type="submit">Save profile</button>
|
||||
<p>If you have not downloaded the plugin yet, <a href="https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi" download>find it here</a>.</p>
|
||||
<p id="profile-status" class="status" role="status"></p>
|
||||
</form>
|
||||
</section>
|
||||
@@ -144,7 +145,7 @@
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a
|
||||
<footer class="site-footer">LinkLog Version {{ app_version }}. Copyright © 2026 Olaf Kolkman · <a
|
||||
href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
|
||||
<script src="/static/auth-header.js?v=3"></script>
|
||||
<script src="/static/logout.js?v=2"></script>
|
||||
|
||||
@@ -9,61 +9,66 @@ from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json'
|
||||
FRONTEND_VERSION_PATH = ROOT / 'frontend' / 'version.json'
|
||||
SETTINGS_PATH = ROOT / 'backend' / 'app' / 'core' / 'config.py'
|
||||
UPDATES_PATH = ROOT / 'webextension' / 'updates.json'
|
||||
SIGNED_DIR = ROOT / 'XPI' / 'signed'
|
||||
VERSION_RE = re.compile(r'\d+\.\d+\.\d+')
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(f'release validation failed: {message}')
|
||||
|
||||
|
||||
def version_key(version: str) -> tuple[int, int, int]:
|
||||
return tuple(int(part) for part in version.split('.'))
|
||||
|
||||
|
||||
def find_latest_signed_xpi() -> tuple[str, Path]:
|
||||
candidates = []
|
||||
for xpi_path in SIGNED_DIR.glob('LinkLog-*.xpi'):
|
||||
match = re.fullmatch(r'LinkLog-(\d+\.\d+\.\d+)\.xpi', xpi_path.name)
|
||||
if match:
|
||||
candidates.append((match.group(1), xpi_path))
|
||||
if not candidates:
|
||||
fail(f'no signed plugin artifacts found in {SIGNED_DIR.relative_to(ROOT)}')
|
||||
return max(candidates, key=lambda candidate: version_key(candidate[0]))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
manifest = json.loads(MANIFEST_PATH.read_text())
|
||||
extension_version = manifest.get('version')
|
||||
if not isinstance(extension_version, str) or not re.fullmatch(r'\d+\.\d+\.\d+', extension_version):
|
||||
fail('webextension/manifest.json has no valid three-part version')
|
||||
|
||||
frontend_version = json.loads(FRONTEND_VERSION_PATH.read_text()).get('version')
|
||||
if frontend_version != extension_version:
|
||||
fail(f'frontend version {frontend_version} does not match extension version {extension_version}')
|
||||
|
||||
gecko_settings = manifest.get('browser_specific_settings', {}).get('gecko', {})
|
||||
data_permissions = gecko_settings.get('data_collection_permissions')
|
||||
if data_permissions != {'required': ['websiteActivity'], 'optional': []}:
|
||||
fail('Firefox data_collection_permissions must require websiteActivity and have no optional categories')
|
||||
|
||||
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)
|
||||
if backend_version != extension_version:
|
||||
fail(f'backend version {backend_version} does not match extension version {extension_version}')
|
||||
if not VERSION_RE.fullmatch(backend_version):
|
||||
fail(f'backend version {backend_version} is not a valid three-part version')
|
||||
|
||||
xpi_path = SIGNED_DIR / f'LinkLog-{extension_version}.xpi'
|
||||
if not xpi_path.is_file():
|
||||
fail(f'missing manually signed artifact: {xpi_path.relative_to(ROOT)}')
|
||||
extension_version, xpi_path = find_latest_signed_xpi()
|
||||
with zipfile.ZipFile(xpi_path) as archive:
|
||||
try:
|
||||
packaged_manifest = json.loads(archive.read('manifest.json'))
|
||||
except KeyError:
|
||||
fail('signed XPI does not contain manifest.json')
|
||||
if packaged_manifest.get('version') != extension_version:
|
||||
fail('signed XPI manifest version does not match webextension/manifest.json')
|
||||
fail('signed XPI manifest version does not match its filename')
|
||||
gecko_settings = packaged_manifest.get('browser_specific_settings', {}).get('gecko', {})
|
||||
data_permissions = gecko_settings.get('data_collection_permissions')
|
||||
if data_permissions != {'required': ['websiteActivity'], 'optional': []}:
|
||||
fail('Firefox data_collection_permissions must require websiteActivity and have no optional categories')
|
||||
if archive.testzip() is not None:
|
||||
fail('signed XPI contains a corrupt member')
|
||||
|
||||
updates = json.loads(UPDATES_PATH.read_text())
|
||||
addon_id = gecko_settings['id']
|
||||
update_entries = updates.get('addons', {}).get(addon_id, {}).get('updates', [])
|
||||
if not any(entry.get('version') == extension_version for entry in update_entries):
|
||||
fail(f'webextension/updates.json has no update entry for {extension_version}')
|
||||
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:
|
||||
print(f'backend_version={backend_version}', file=output)
|
||||
print(f'plugin_version={extension_version}', file=output)
|
||||
print(f'signed_xpi={signed_xpi}', file=output)
|
||||
elif len(sys.argv) != 1:
|
||||
fail('usage: validate_release.py [--github-output <path>]')
|
||||
|
||||
print(f'validated LinkLog release {frontend_version}')
|
||||
print(f'xpi={xpi_path.relative_to(ROOT)}')
|
||||
print(f'validated LinkLog backend release {backend_version}')
|
||||
print(f'plugin_version={extension_version}')
|
||||
print(f'signed_xpi={signed_xpi}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user