release infra: build scripts, Firefox signing, plugin fixes

- .gitignore: release/, .env, *.xpi, node_modules/
- .env.example: template for AMO credentials
- extension-firefox/package.json: web-ext scripts (lint, sign)
- extension-firefox/manifest.json: gecko.id + update_url + data_collection_permissions
- scripts/build.sh: renamed binaries (verstak, verstak-server), release target
- scripts/sign-firefox-xpi.sh: AMO signing with --self-hosted
- scripts/release-firefox-xpi.sh: signed XPI + updates.json generation
- scripts/release.sh: full release pipeline (DEB/RPM/checksums/git tag)
- VERSION: 0.1.0
- README.md: Firefox extension & release sections

Plugin fixes:
- internal/core/plugins/manager.go: auto-create .verstak/plugins/ dir
- frontend/src/lib/SettingsSidebar.svelte: remove 'plugins' from disabled + active left border
- frontend/src/lib/SettingsPlugins.svelte: force ListPlugins refresh on toggle
- frontend/src/lib/SettingsWindow.svelte: onPluginToggle callback
- frontend/src/App.svelte: refreshSystemViews on plugin toggle
- frontend/src/lib/CalendarPluginPage.svelte: visible error icon
This commit is contained in:
2026-06-08 11:07:29 +08:00
parent 3b79754f45
commit b1d1defebe
22 changed files with 4792 additions and 30 deletions
+42 -11
View File
@@ -3,7 +3,7 @@ set -e
# ============================================================
# Verstak build script
# Usage: ./scripts/build.sh [gui|server|extensions|all]
# Usage: ./scripts/build.sh [gui|server|extensions|all|release]
# ============================================================
BUILD_DIR="build"
@@ -23,17 +23,17 @@ build_gui() {
# Build Go binary with Wails v2
# Tags: webkit2_41 required for WebKitGTK 2.41+, desktop/production for Wails
go build -tags "webkit2_41 desktop production" -ldflags="-s -w" -o "$BUILD_DIR/verstak-gui-linux-amd64" ./cmd/verstak-gui/
go build -tags "webkit2_41 desktop production" -ldflags="-s -w" -o "$BUILD_DIR/verstak" ./cmd/verstak-gui/
echo "==> GUI binary: $BUILD_DIR/verstak-gui-linux-amd64"
echo "==> GUI binary: $BUILD_DIR/verstak"
}
# --- Server binary ---
build_server() {
echo "==> Building server binary..."
go build -ldflags="-s -w" -o "$BUILD_DIR/verstak-server-linux-amd64" ./cmd/verstak-server/
echo "==> Server binary: $BUILD_DIR/verstak-server-linux-amd64"
go build -ldflags="-s -w" -o "$BUILD_DIR/verstak-server" ./cmd/verstak-server/
echo "==> Server binary: $BUILD_DIR/verstak-server"
}
# --- Chrome extension ---
@@ -62,21 +62,19 @@ build_chrome_extension() {
echo "==> Chrome extension: $out_file"
}
# --- Firefox extension ---
# --- Firefox extension (unsigned) ---
build_firefox_extension() {
echo "==> Building Firefox extension..."
local ext_dir="extension-firefox"
local out_file="$BUILD_DIR/verstak-bridge-firefox.xpi"
local out_file="$BUILD_DIR/verstak-bridge-firefox-unsigned.zip"
if [ ! -d "$ext_dir" ]; then
echo "ERROR: $ext_dir/ directory not found"
return 1
fi
# Firefox xpi is a zip with .xpi extension
# Must include manifest.json at root + icons
cd "$ext_dir"
zip -r "../$out_file" \
manifest.json \
@@ -86,7 +84,7 @@ build_firefox_extension() {
-x "*/.DS_Store" "*/Thumbs.db" "*/__MACOSX/*" "*/.git/*"
cd ..
echo "==> Firefox extension: $out_file"
echo "==> Firefox extension (unsigned): $out_file"
}
# --- All extensions ---
@@ -112,6 +110,38 @@ build_all() {
ls -lh "$BUILD_DIR/"
}
# --- Release: copy to release/linux/ ---
prepare_release() {
local RELEASE_DIR="release/linux"
mkdir -p "$RELEASE_DIR"
if [ -f "$BUILD_DIR/verstak" ]; then
cp "$BUILD_DIR/verstak" "$RELEASE_DIR/verstak"
chmod 755 "$RELEASE_DIR/verstak"
echo " $RELEASE_DIR/verstak"
else
echo " WARNING: $BUILD_DIR/verstak not found, skipping"
fi
if [ -f "$BUILD_DIR/verstak-server" ]; then
cp "$BUILD_DIR/verstak-server" "$RELEASE_DIR/verstak-server"
chmod 755 "$RELEASE_DIR/verstak-server"
echo " $RELEASE_DIR/verstak-server"
else
echo " WARNING: $BUILD_DIR/verstak-server not found, skipping"
fi
}
build_release() {
echo "==> Building all + preparing release/linux/..."
build_all
prepare_release
echo ""
echo "==> Release binaries in release/linux/"
ls -lh "release/linux/"
}
# --- Main ---
case "${1:-all}" in
@@ -120,9 +150,10 @@ case "${1:-all}" in
extensions) build_extensions ;;
chrome) mkdir -p "$BUILD_DIR"; build_chrome_extension ;;
firefox) mkdir -p "$BUILD_DIR"; build_firefox_extension ;;
release) build_release ;;
all) build_all ;;
*)
echo "Usage: $0 [gui|server|extensions|chrome|firefox|all]"
echo "Usage: $0 [gui|server|extensions|chrome|firefox|release|all]"
exit 1
;;
esac
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# release-firefox-xpi.sh
# Builds a signed Firefox XPI, copies it to release/firefox/ with a clean name,
# and generates updates.json for self-distributed updates.
# Run from repo root.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
if [[ -f ".env" ]]; then
set -a
source ".env"
set +a
fi
SOURCE_DIR="${WEB_EXT_SOURCE_DIR:-extension-firefox}"
ARTIFACTS_DIR="${WEB_EXT_ARTIFACTS_DIR:-web-ext-artifacts}"
RELEASE_DIR="release/firefox"
UPDATE_BASE_URL="${VERSTAK_FIREFOX_UPDATE_BASE_URL:-https://mirv.top/verstak/firefox}"
# Step 1: sign the XPI
./scripts/sign-firefox-xpi.sh
# Step 2: read version and addon ID from manifest
if [[ ! -f "$SOURCE_DIR/manifest.json" ]]; then
echo "ERROR: $SOURCE_DIR/manifest.json not found" >&2
exit 1
fi
VERSION="$(node -e "console.log(require('./${SOURCE_DIR}/manifest.json').version)")"
if [[ -z "$VERSION" ]]; then
echo "ERROR: could not read version from $SOURCE_DIR/manifest.json" >&2
exit 1
fi
ADDON_ID="$(node -e "console.log(require('./${SOURCE_DIR}/manifest.json').browser_specific_settings.gecko.id)")"
if [[ -z "$ADDON_ID" ]]; then
echo "ERROR: could not read browser_specific_settings.gecko.id from $SOURCE_DIR/manifest.json" >&2
exit 1
fi
echo "Extension version: $VERSION"
echo "Addon ID: $ADDON_ID"
# Step 3: locate the signed XPI
SIGNED_XPI="$(find "$ARTIFACTS_DIR" -maxdepth 1 -type f -name '*.xpi' | sort | tail -n 1)"
if [[ -z "$SIGNED_XPI" ]]; then
echo "ERROR: no signed XPI found in $ARTIFACTS_DIR" >&2
exit 1
fi
# Step 4: copy to release directory
mkdir -p "$RELEASE_DIR"
RELEASE_XPI="verstak-firefox-${VERSION}.xpi"
cp "$SIGNED_XPI" "$RELEASE_DIR/$RELEASE_XPI"
echo "Copied signed XPI to $RELEASE_DIR/$RELEASE_XPI"
# Step 5: generate updates.json
cat > "$RELEASE_DIR/updates.json" <<EOF
{
"addons": {
"${ADDON_ID}": {
"updates": [
{
"version": "${VERSION}",
"update_link": "${UPDATE_BASE_URL}/${RELEASE_XPI}"
}
]
}
}
}
EOF
echo "Generated $RELEASE_DIR/updates.json"
# Step 6: summary
echo ""
echo "=== Firefox release artifacts ==="
echo "$RELEASE_DIR/$RELEASE_XPI"
echo "$RELEASE_DIR/updates.json"
echo ""
echo "Upload to server:"
echo " ${UPDATE_BASE_URL}/${RELEASE_XPI}"
echo " ${UPDATE_BASE_URL}/updates.json"
+294
View File
@@ -0,0 +1,294 @@
#!/usr/bin/env bash
# release.sh — Verstak release script
# Builds all binaries, packages, signs Firefox XPI, creates git tag.
#
# Usage:
# ./scripts/release.sh # Full release (requires AMO tokens for Firefox signing)
# ./scripts/release.sh --skip-firefox-sign # Skip Firefox XPI signing
# ./scripts/release.sh --publish # Push git tag + create GitHub release
# ./scripts/release.sh --help # Show usage
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
# --- Config ---
SKIP_FIREFOX_SIGN=false
PUBLISH=false
for arg in "$@"; do
case "$arg" in
--skip-firefox-sign) SKIP_FIREFOX_SIGN=true ;;
--publish) PUBLISH=true ;;
--help)
echo "Usage: $0 [--skip-firefox-sign] [--publish]"
echo ""
echo " --skip-firefox-sign Skip Firefox extension signing (fails by default if AMO tokens missing)"
echo " --publish Push git tag and create GitHub release"
exit 0
;;
esac
done
VERSION="$(cat VERSION 2>/dev/null || echo "0.1.0-dev")"
RELEASE_DIR="release"
LINUX_DIR="$RELEASE_DIR/linux"
BUILD_DIR="build"
echo "============================================"
echo " Verstak Release v$VERSION"
echo "============================================"
echo ""
# --- Pre-flight checks ---
echo "==> Pre-flight checks..."
if ! git diff --quiet --exit-code 2>/dev/null; then
echo "ERROR: working tree is dirty. Commit or stash changes first."
git diff --stat
exit 1
fi
if ! git diff --cached --quiet --exit-code 2>/dev/null; then
echo "ERROR: staged but uncommitted changes. Commit first."
exit 1
fi
echo " Working tree clean ✓"
if ! go version &>/dev/null; then
echo "ERROR: go not found"
exit 1
fi
echo " Go: $(go version)"
# --- Run tests ---
echo ""
echo "==> Running tests..."
go vet ./...
go test ./... -count=1
echo " Tests: all passed ✓"
# --- Build ---
echo ""
echo "==> Building all binaries..."
mkdir -p "$BUILD_DIR" "$LINUX_DIR"
rm -f "$LINUX_DIR"/*
./scripts/build.sh release
echo " Build complete ✓"
# --- VERSION file ---
echo "$VERSION" > "$LINUX_DIR/VERSION"
# --- DEB package: GUI ---
echo ""
echo "==> Building DEB packages..."
build_deb() {
local binary="$1"
local pkg_name="$2"
local description="$3"
local systemd_unit="${4:-}"
local deb_dir="$BUILD_DIR/deb-tmp/$pkg_name"
mkdir -p "$deb_dir/DEBIAN"
mkdir -p "$deb_dir/usr/local/bin"
mkdir -p "$deb_dir/usr/share/doc/$pkg_name"
cp "$BUILD_DIR/$binary" "$deb_dir/usr/local/bin/$pkg_name"
chmod 755 "$deb_dir/usr/local/bin/$pkg_name"
if [[ -n "$systemd_unit" && -f "$systemd_unit" ]]; then
mkdir -p "$deb_dir/lib/systemd/system"
cp "$systemd_unit" "$deb_dir/lib/systemd/system/"
fi
# Simple control file
cat > "$deb_dir/DEBIAN/control" <<CTRL
Package: $pkg_name
Version: $VERSION
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Verstak <verstak@mirv.top>
Description: $description
CTRL
# Copy README if exists
[[ -f "README.md" ]] && cp "README.md" "$deb_dir/usr/share/doc/$pkg_name/"
dpkg-deb --root-owner-group --build "$deb_dir" "$LINUX_DIR/${pkg_name}.deb" >/dev/null
rm -rf "$BUILD_DIR/deb-tmp"
echo " $LINUX_DIR/${pkg_name}.deb"
}
build_deb "verstak" "verstak" "Verstak — local-first working vault GUI"
build_deb "verstak-server" "verstak-server" "Verstak Sync Server" "cmd/verstak-server/verstak-server.service"
# --- RPM package: GUI ---
echo ""
echo "==> Building RPM packages..."
build_rpm() {
local binary="$1"
local pkg_name="$2"
local description="$3"
local systemd_unit="${4:-}"
local rpm_dir="$BUILD_DIR/rpm-tmp"
mkdir -p "$rpm_dir/RPMS/x86_64"
mkdir -p "$rpm_dir/BUILD"
mkdir -p "$rpm_dir/SOURCES"
# Build root for rpmbuild
local buildroot="$rpm_dir/buildroot"
mkdir -p "$buildroot/usr/local/bin"
mkdir -p "$buildroot/usr/share/doc/$pkg_name"
cp "$BUILD_DIR/$binary" "$buildroot/usr/local/bin/$pkg_name"
chmod 755 "$buildroot/usr/local/bin/$pkg_name"
if [[ -n "$systemd_unit" && -f "$systemd_unit" ]]; then
mkdir -p "$buildroot/lib/systemd/system"
cp "$systemd_unit" "$buildroot/lib/systemd/system/"
fi
[[ -f "README.md" ]] && cp "README.md" "$buildroot/usr/share/doc/$pkg_name/"
# File list
local file_list
file_list="%attr(755, root, root) /usr/local/bin/$pkg_name"
file_list+="%attr(644, root, root) /usr/share/doc/$pkg_name/*"
if [[ -n "$systemd_unit" && -f "$systemd_unit" ]]; then
# systemd file will be included via %files section
file_list+="%attr(644, root, root) /lib/systemd/system/$(basename "$systemd_unit")"
fi
local spec_file="$rpm_dir/SPECS/${pkg_name}.spec"
mkdir -p "$(dirname "$spec_file")"
cat > "$spec_file" <<SPEC
Summary: $description
Name: $pkg_name
Version: $VERSION
Release: 1
License: MIT
Group: Utilities
BuildArch: x86_64
AutoReqProv: no
Prefix: /usr
%description
$description
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/usr/local/bin
cp $BUILD_DIR/$binary %{buildroot}/usr/local/bin/$pkg_name
chmod 755 %{buildroot}/usr/local/bin/$pkg_name
SPEC
if [[ -n "$systemd_unit" && -f "$systemd_unit" ]]; then
cat >> "$spec_file" <<SPEC
mkdir -p %{buildroot}/lib/systemd/system
cp $systemd_unit %{buildroot}/lib/systemd/system/
SPEC
fi
cat >> "$spec_file" <<SPEC
mkdir -p %{buildroot}/usr/share/doc/$pkg_name
cp -r README.md %{buildroot}/usr/share/doc/$pkg_name/ 2>/dev/null || true
%files
/usr/local/bin/$pkg_name
SPEC
if [[ -n "$systemd_unit" && -f "$systemd_unit" ]]; then
echo "/lib/systemd/system/$(basename "$systemd_unit")" >> "$spec_file"
fi
cat >> "$spec_file" <<SPEC
%doc /usr/share/doc/$pkg_name/*
SPEC
rpmbuild --define "_topdir $rpm_dir" \
--define "_builddir $rpm_dir/BUILD" \
--define "_sourcedir $rpm_dir/SOURCES" \
--define "_rpmdir $rpm_dir/RPMS" \
--define "_specdir $rpm_dir/SPECS" \
--define "_buildrootdir $rpm_dir/buildroot" \
-bb "$spec_file" >/dev/null
cp "$rpm_dir/RPMS/x86_64/${pkg_name}-${VERSION}-1.x86_64.rpm" \
"$LINUX_DIR/${pkg_name}.rpm"
rm -rf "$rpm_dir"
echo " $LINUX_DIR/${pkg_name}.rpm"
}
build_rpm "verstak" "verstak" "Verstak — local-first working vault GUI"
build_rpm "verstak-server" "verstak-server" "Verstak Sync Server" "cmd/verstak-server/verstak-server.service"
# --- Firefox signed XPI ---
echo ""
if [[ "$SKIP_FIREFOX_SIGN" == "true" ]]; then
echo "==> Firefox extension signing skipped (--skip-firefox-sign)"
elif [[ -f ".env" ]] && grep -q "WEB_EXT_API_KEY" ".env" 2>/dev/null; then
echo "==> Signing Firefox extension..."
./scripts/release-firefox-xpi.sh
else
echo "ERROR: AMO tokens not found (WEB_EXT_API_KEY / WEB_EXT_API_SECRET in .env)." >&2
echo " Firefox signing is required for a full release." >&2
echo " To skip: run with --skip-firefox-sign" >&2
echo " To sign: copy .env.example -> .env and fill in AMO credentials from" >&2
echo " https://addons.mozilla.org/developers/" >&2
exit 1
fi
# --- Checksums ---
echo ""
echo "==> Generating checksums..."
cd "$LINUX_DIR"
sha256sum verstak verstak-server *.deb *.rpm 2>/dev/null > checksums.txt || true
cd "$ROOT_DIR"
echo " $LINUX_DIR/checksums.txt"
# --- Summary ---
echo ""
echo "============================================"
echo " Release v$VERSION artifacts"
echo "============================================"
echo ""
echo "Linux binaries:"
ls -lh "$LINUX_DIR/" 2>/dev/null
echo ""
if [[ -d "$RELEASE_DIR/firefox" ]]; then
echo "Firefox extension:"
ls -lh "$RELEASE_DIR/firefox/" 2>/dev/null
echo ""
fi
echo "============================================"
# --- Git tag + release ---
if [[ "$PUBLISH" == "true" ]]; then
echo ""
echo "==> Creating git tag v$VERSION..."
git tag -s "v$VERSION" -m "Verstak v$VERSION"
git push origin "v$VERSION"
echo "==> Creating GitHub release..."
gh release create "v$VERSION" \
--title "Verstak v$VERSION" \
--notes "See CHANGELOG.md for details" \
"$LINUX_DIR"/* \
$([[ -d "$RELEASE_DIR/firefox" ]] && echo "$RELEASE_DIR/firefox/"* || true)
echo " GitHub release created: https://github.com/$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\\.git$||')/releases/tag/v$VERSION"
else
echo ""
echo "Dry-run: use --publish to create git tag + GitHub release."
echo "Run: git tag -s v$VERSION -m \"Verstak v$VERSION\""
fi
echo ""
echo "Done."
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# sign-firefox-xpi.sh
# Signs the Firefox extension with AMO (Mozilla Add-ons).
# Run from repo root. Reads .env for AMO credentials.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
if [[ -f ".env" ]]; then
set -a
source ".env"
set +a
fi
SOURCE_DIR="${WEB_EXT_SOURCE_DIR:-extension-firefox}"
ARTIFACTS_DIR="${WEB_EXT_ARTIFACTS_DIR:-web-ext-artifacts}"
CHANNEL="${WEB_EXT_CHANNEL:-unlisted}"
if [[ -z "${WEB_EXT_API_KEY:-}" ]]; then
echo "ERROR: WEB_EXT_API_KEY is not set. Put AMO JWT issuer into .env" >&2
exit 1
fi
if [[ -z "${WEB_EXT_API_SECRET:-}" ]]; then
echo "ERROR: WEB_EXT_API_SECRET is not set. Put AMO JWT secret into .env" >&2
exit 1
fi
if [[ "$CHANNEL" != "unlisted" ]]; then
echo "ERROR: only WEB_EXT_CHANNEL=unlisted is allowed for self-distributed Verstak Firefox builds" >&2
exit 1
fi
if [[ ! -f "$SOURCE_DIR/manifest.json" ]]; then
echo "ERROR: manifest.json not found in $SOURCE_DIR" >&2
exit 1
fi
mkdir -p "$ARTIFACTS_DIR"
echo "Linting Firefox extension..."
npx web-ext lint \
--source-dir "$SOURCE_DIR" \
--self-hosted
echo "Signing Firefox extension as unlisted/self-distributed XPI..."
SIGN_ARGS=(
sign
--source-dir "$SOURCE_DIR"
--artifacts-dir "$ARTIFACTS_DIR"
--channel "$CHANNEL"
--self-hosted
--api-key "$WEB_EXT_API_KEY"
--api-secret "$WEB_EXT_API_SECRET"
)
if [[ -n "${WEB_EXT_API_PROXY:-}" ]]; then
SIGN_ARGS+=(--api-proxy "$WEB_EXT_API_PROXY")
fi
npx web-ext "${SIGN_ARGS[@]}"
SIGNED_XPI="$(find "$ARTIFACTS_DIR" -maxdepth 1 -type f -name '*.xpi' | sort | tail -n 1 || true)"
if [[ -z "$SIGNED_XPI" ]]; then
echo "ERROR: signed XPI was not created in $ARTIFACTS_DIR" >&2
exit 1
fi
echo "Signed XPI created:"
echo "$SIGNED_XPI"