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:
parent
3b79754f45
commit
b1d1defebe
|
|
@ -0,0 +1,22 @@
|
|||
# Verstak Firefox Extension — environment template
|
||||
# Copy to .env and fill in real values.
|
||||
# DO NOT commit .env — it contains AMO secrets.
|
||||
|
||||
# AMO API credentials (from https://addons.mozilla.org/developers/)
|
||||
WEB_EXT_API_KEY=
|
||||
WEB_EXT_API_SECRET=
|
||||
|
||||
# Build channel: "unlisted" for self-distributed builds
|
||||
WEB_EXT_CHANNEL=unlisted
|
||||
|
||||
# Source directory for the Firefox extension
|
||||
WEB_EXT_SOURCE_DIR=extension-firefox
|
||||
|
||||
# Output directory for signed XPI artifacts
|
||||
WEB_EXT_ARTIFACTS_DIR=web-ext-artifacts
|
||||
|
||||
# Base URL for Firefox self-hosted updates
|
||||
VERSTAK_FIREFOX_UPDATE_BASE_URL=https://mirv.top/verstak/firefox
|
||||
|
||||
# Optional: HTTP proxy for AMO API requests (e.g. http://localhost:12334)
|
||||
# WEB_EXT_API_PROXY=
|
||||
|
|
@ -49,3 +49,17 @@ server-data/
|
|||
# Build output
|
||||
build/
|
||||
build.log
|
||||
|
||||
# Release artifacts
|
||||
release/
|
||||
|
||||
# Environment / secrets
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Firefox extension
|
||||
web-ext-artifacts/
|
||||
*.xpi
|
||||
*.zip
|
||||
node_modules/
|
||||
|
|
|
|||
74
README.md
74
README.md
|
|
@ -70,22 +70,88 @@
|
|||
Дополнительно запускается headless Chromium smoke через Wails-mock: проверяются first-run, recovery, основное окно, Settings, workspace, вкладки дела, файлы, журнал, активность и мобильный viewport. Smoke выполняет реальные UI-действия: создание заметки, запись worklog, создание узла, вход в папку и возврат назад, а также Sync Now с предупреждениями о conflicts/applyErrors. Скриншоты пишутся в `/tmp/verstak-gui-smoke`.
|
||||
|
||||
Бинарники попадают в `build/`:
|
||||
- `verstak-gui-linux-amd64` — GUI-приложение
|
||||
- `verstak-server-linux-amd64` — опциональный сервер синхронизации
|
||||
- `verstak` — GUI-приложение
|
||||
- `verstak-server` — опциональный сервер синхронизации
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
# GUI (после сборки)
|
||||
./build/verstak-gui-linux-amd64
|
||||
./build/verstak
|
||||
|
||||
# Сервер (после сборки)
|
||||
./build/verstak-server-linux-amd64 --help
|
||||
./build/verstak-server --help
|
||||
|
||||
# CLI
|
||||
go run ./cmd/verstak/ --help
|
||||
```
|
||||
|
||||
## Firefox Extension
|
||||
|
||||
Расширение для Firefox — `extension-firefox/`. Распространяется как signed XPI: **Mozilla только подписывает** XPI через AMO (unlisted channel), а мы самостоятельно хостим signed XPI и управляем обновлениями через `updates.json`.
|
||||
|
||||
### Сборка (unsigned)
|
||||
|
||||
```bash
|
||||
./scripts/build.sh firefox
|
||||
# → build/verstak-bridge-firefox-unsigned.zip
|
||||
```
|
||||
|
||||
### Подпись (требуются AMO-токены)
|
||||
|
||||
```bash
|
||||
# 1. Скопировать .env.example → .env и заполнить WEB_EXT_API_KEY / WEB_EXT_API_SECRET
|
||||
cp .env.example .env
|
||||
|
||||
# 2. Установить зависимости
|
||||
cd extension-firefox && npm install && cd ..
|
||||
|
||||
# 3. Подписать XPI
|
||||
./scripts/sign-firefox-xpi.sh
|
||||
# → web-ext-artifacts/*.xpi
|
||||
|
||||
# 4. Полный релиз: подпись + release/firefox/ + updates.json
|
||||
./scripts/release-firefox-xpi.sh
|
||||
# → release/firefox/verstak-firefox-VERSION.xpi
|
||||
# → release/firefox/updates.json
|
||||
```
|
||||
|
||||
### Firefox Release Artifacts
|
||||
|
||||
```
|
||||
release/firefox/verstak-firefox-VERSION.xpi
|
||||
release/firefox/updates.json
|
||||
```
|
||||
|
||||
Обновления: Firefox проверяет `update_url` из manifest.json, указывающий на наш `updates.json`. При выходе новой версии достаточно:
|
||||
1. Подписать новый XPI
|
||||
2. Заменить файл на сервере
|
||||
3. Обновить `updates.json`
|
||||
|
||||
## Release
|
||||
|
||||
```bash
|
||||
# Полная сборка с упаковкой в DEB/RPM
|
||||
./scripts/release.sh # dry-run (без git tag); требует AMO токены
|
||||
./scripts/release.sh --publish # с git tag + GitHub release
|
||||
|
||||
# Без подписи Firefox
|
||||
./scripts/release.sh --skip-firefox-sign
|
||||
```
|
||||
|
||||
Артефакты релиза (после `./scripts/release.sh`):
|
||||
|
||||
```
|
||||
release/linux/verstak # GUI binary
|
||||
release/linux/verstak-server # Server binary
|
||||
release/linux/verstak.deb # DEB-пакет GUI
|
||||
release/linux/verstak-server.deb # DEB-пакет сервера
|
||||
release/linux/verstak.rpm # RPM-пакет GUI
|
||||
release/linux/verstak-server.rpm # RPM-пакет сервера
|
||||
release/firefox/verstak-firefox-VERSION.xpi # Signed XPI
|
||||
release/firefox/updates.json # Firefox update manifest
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -19,8 +19,8 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-C2sdkP-s.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-Cjkp2F09.css">
|
||||
<script type="module" crossorigin src="/assets/main-CMk8guXZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BecQca8b.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "verstak-bridge@verstak.app",
|
||||
"strict_min_version": "109.0"
|
||||
"strict_min_version": "115.0",
|
||||
"update_url": "https://mirv.top/verstak/firefox/updates.json",
|
||||
"data_collection_permissions": {
|
||||
"required": ["none"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "verstak-bridge-firefox",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Verstak Bridge Firefox extension",
|
||||
"scripts": {
|
||||
"lint:firefox": "web-ext lint --source-dir .",
|
||||
"build:firefox": "web-ext build --source-dir . --artifacts-dir ../web-ext-artifacts --overwrite-dest",
|
||||
"sign:firefox": "../scripts/sign-firefox-xpi.sh",
|
||||
"release:firefox": "../scripts/release-firefox-xpi.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"web-ext": "^8.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -2523,6 +2523,14 @@
|
|||
showSettings = false
|
||||
}
|
||||
|
||||
async function refreshSystemViews() {
|
||||
try {
|
||||
systemViews = await wailsCall('ListSystemViewsWithPlugins') || []
|
||||
} catch (e) {
|
||||
// Fallback: keep current views
|
||||
}
|
||||
}
|
||||
|
||||
function syncResultMessage(result) {
|
||||
const conflicts = Array.isArray(result?.conflicts) ? result.conflicts : []
|
||||
const applyErrors = Array.isArray(result?.applyErrors) ? result.applyErrors : []
|
||||
|
|
@ -3873,7 +3881,7 @@
|
|||
{/if}
|
||||
|
||||
{#if showSettings}
|
||||
<SettingsWindow onClose={closeSettings} onSyncRefresh={loadSyncStatus} initialSection={settingsInitialSection} />
|
||||
<SettingsWindow onClose={closeSettings} onSyncRefresh={loadSyncStatus} onPluginToggle={refreshSystemViews} initialSection={settingsInitialSection} />
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@
|
|||
{#if loading}
|
||||
<p class="loading">Загрузка…</p>
|
||||
{:else if error}
|
||||
<p class="error">{error}</p>
|
||||
<p class="error">⚠ {error}</p>
|
||||
{:else if htmlPanel}
|
||||
<iframe
|
||||
bind:this={iframeEl}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import { onMount } from 'svelte'
|
||||
import { t } from './i18n'
|
||||
|
||||
export let onPluginToggle = null
|
||||
|
||||
let plugins = []
|
||||
let loading = true
|
||||
let error = ''
|
||||
|
|
@ -29,7 +31,9 @@
|
|||
const newState = !p.active
|
||||
try {
|
||||
await wailsCall('SetPluginEnabled', p.name, newState)
|
||||
p.active = newState
|
||||
// Force refresh from backend — ensures UI stays in sync
|
||||
plugins = await wailsCall('ListPlugins') || []
|
||||
if (onPluginToggle) onPluginToggle()
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
}
|
||||
|
|
@ -42,6 +46,7 @@
|
|||
p.installed = true
|
||||
// Refresh list to reflect new state
|
||||
plugins = await wailsCall('ListPlugins') || []
|
||||
if (onPluginToggle) onPluginToggle()
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
}
|
||||
|
|
@ -55,6 +60,7 @@
|
|||
p.installed = false
|
||||
p.active = false
|
||||
plugins = await wailsCall('ListPlugins') || []
|
||||
if (onPluginToggle) onPluginToggle()
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<button
|
||||
class="settings-nav-item"
|
||||
class:active={activeSection === sec.id}
|
||||
class:disabled={sec.id === 'plugins' || sec.id === 'files' || sec.id === 'activity' || sec.id === 'backup'}
|
||||
class:disabled={sec.id === 'files' || sec.id === 'activity' || sec.id === 'backup'}
|
||||
on:click={() => select(sec.id)}
|
||||
>
|
||||
<span class="settings-nav-icon">
|
||||
|
|
@ -101,6 +101,17 @@
|
|||
background: var(--accent-bg, rgba(99, 102, 241, 0.15));
|
||||
color: var(--accent, #818cf8);
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
}
|
||||
.settings-nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 3px;
|
||||
background: var(--accent, #818cf8);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
.settings-nav-item.disabled {
|
||||
opacity: 0.4;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
export let onClose = null
|
||||
export let onSyncRefresh = null
|
||||
export let onPluginToggle = null
|
||||
export let initialSection = 'general'
|
||||
|
||||
let activeSection = initialSection
|
||||
|
|
@ -82,7 +83,7 @@
|
|||
{:else if activeSection === 'templates'}
|
||||
<SettingsTemplates onRefresh={loadConfig} />
|
||||
{:else if activeSection === 'plugins'}
|
||||
<SettingsPlugins />
|
||||
<SettingsPlugins {onPluginToggle} />
|
||||
{:else if activeSection === 'files'}
|
||||
<SettingsFiles />
|
||||
{:else if activeSection === 'activity'}
|
||||
|
|
|
|||
|
|
@ -110,10 +110,16 @@ func NewManager(vaultRoot string) *Manager {
|
|||
// Active/Enabled are always false after Discover — call SyncConfig + InitRuntimes to activate.
|
||||
func (m *Manager) Discover() {
|
||||
pluginsDir := filepath.Join(m.vaultRoot, ".verstak", "plugins")
|
||||
// Ensure plugins directory exists
|
||||
if err := os.MkdirAll(pluginsDir, 0o750); err != nil {
|
||||
log.Printf("[plugins] failed to create plugins dir: %v", err)
|
||||
m.plugins = nil
|
||||
return
|
||||
}
|
||||
entries, err := os.ReadDir(pluginsDir)
|
||||
if err != nil {
|
||||
m.plugins = nil
|
||||
return // no plugins dir — OK
|
||||
return
|
||||
}
|
||||
|
||||
newPlugins := make([]Plugin, 0, len(entries))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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."
|
||||
|
|
@ -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"
|
||||
Loading…
Reference in New Issue