20 changed files with 784 additions and 88 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# .gitignore
frontend/node_modules/
frontend/dist/
build/bin/verstak-desktop
build/
smoke-platform
plugins/
vendor/
+178 -51
View File
@@ -1,79 +1,206 @@
# Verstak Desktop
# Verstak
Verstak is a local-first desktop workspace for files, notes, browser captures,
activity and work journal entries. This repository contains the Go/Wails desktop
host and UI shell; user-facing functions are delivered by official plugins.
Verstak is a local-first workspace for files, notes, browser captures,
activity and work-journal entries. This repository is the desktop application:
the Go/Wails host and UI shell that loads plugins from a `plugins/` directory
next to its executable.
> **Alpha software.** Use a disposable vault while evaluating it. APIs, storage
> formats and packaging can change before the first stable release.
## What the alpha does
- keeps each workspace's identity independent of its current directory name;
- keeps browser captures in a reviewable Inbox, with archive and restore rather
than a destructive “remove from inbox” action;
- records local file/note activity and, after review, turns it into journal
entries without creating a workspace automatically;
- accepts optional browser domain-time batches. The browser extension is
opt-in: it sends only normalized domain names and bounded durations, never
URLs, page titles, page contents, keystrokes or navigation history;
- runs official plugins from a `plugins/` directory next to the executable.
## Components
Clone these repositories as siblings when building a complete local setup:
The public alpha is split into small repositories. Keep their `main` branches
in the same release line when building from source.
| Repository | Purpose |
| --- | --- |
| `verstak-desktop` | desktop host and UI shell |
| `verstak-official-plugins` | Files, Notes, Browser Inbox, Activity, Journal and other official plugins |
| `verstak-browser-extension` | Chromium/Firefox capture and optional domain-activity extension |
| `verstak-sdk` | plugin manifest schema and TypeScript API |
| Component | Repository | What it is for |
| --- | --- | --- |
| Desktop | [mirivlad/verstak](https://github.com/mirivlad/verstak) | This application: vault UI, local host and plugin runtime. |
| Official plugins | [mirivlad/verstak-official-plugins](https://github.com/mirivlad/verstak-official-plugins) | Files, Notes, Browser Inbox, Activity, Journal, Sync, Todo and other first-party plugins. |
| Browser extension | [mirivlad/verstak-browser-extension](https://github.com/mirivlad/verstak-browser-extension) | Manual browser captures and opt-in domain-time activity. |
| Sync server | [mirivlad/verstak-sync-server](https://github.com/mirivlad/verstak-sync-server) | Optional self-hosted synchronization between devices. |
| Plugin SDK | [mirivlad/verstak-sdk](https://github.com/mirivlad/verstak-sdk) | TypeScript API, JSON schemas and contract tests for plugin authors. |
| Architecture documentation | [mirivlad/verstak-docs](https://github.com/mirivlad/verstak-docs) | Product, platform and plugin-system design documents. |
## Build from source (Linux)
No server account or browser extension is needed for a local desktop vault.
Requirements: Go, Node.js with npm, Python 3, the [Wails v2 build
prerequisites](https://wails.io/docs/gettingstarted/installation/), and the
WebKitGTK development package for your distribution.
## Build the desktop and official plugins
To build from source, install Go 1.24+, Node.js 20+ with npm, Python 3, the [Wails v2 build
prerequisites](https://wails.io/docs/gettingstarted/installation/), and your
distribution's WebKitGTK development package.
Clone the repositories as siblings. The directory names below are intentional:
the desktop helper finds the official-plugin checkout at
`../verstak-official-plugins`.
```text
verstak-workspace/
├── verstak/
├── verstak-sdk/
├── verstak-official-plugins/
└── verstak-browser-extension/ # optional for browser integration
```
```bash
git clone https://git.mirv.top/verstak/verstak-sdk.git
git clone https://git.mirv.top/verstak/verstak-official-plugins.git
git clone https://git.mirv.top/verstak/verstak-desktop.git
git clone https://git.mirv.top/verstak/verstak-browser-extension.git
git clone https://github.com/mirivlad/verstak.git verstak
git clone https://github.com/mirivlad/verstak-sdk.git verstak-sdk
git clone https://github.com/mirivlad/verstak-official-plugins.git verstak-official-plugins
git clone https://github.com/mirivlad/verstak-browser-extension.git verstak-browser-extension
cd verstak-sdk && ./scripts/build.sh
cd ../verstak-official-plugins && ./scripts/build.sh
cd ../verstak-desktop
cd ../verstak
./scripts/install-dev-plugins.sh
./scripts/build.sh
```
The Linux executable is placed in `build/bin/`. Start it with `--debug` to
display internal plugin-provider IDs and write diagnostic logs.
## Release artifacts
Maintainers can create a Linux desktop tarball after the sibling repositories
above have been built:
`install-dev-plugins.sh` copies the packages from
`../verstak-official-plugins/dist/` into this repository's `plugins/`
directory. `build.sh` then copies them to `build/bin/plugins/`, beside the
desktop executable:
```bash
cd verstak-desktop
./scripts/release.sh v0.1.0-alpha.1
./build/bin/verstak-desktop
```
It writes `release/verstak-desktop-linux-amd64-<version>.tar.gz` and a matching
`SHA256SUMS` file. The archive is self-contained: unpack it and run the
included executable. Browser and SDK packages have their own release scripts
in their repositories.
For a manually assembled installation, place each unpacked plugin directory
directly in `plugins/` beside `verstak-desktop`; for example,
`plugins/browser-inbox/plugin.json`. Do not put the release archive itself in
that directory. The desktop release archive already includes its matching
`plugins/` directory.
## Privacy and activity tracking
Start with `--debug` to show internal plugin-provider identifiers and write
diagnostic logs:
The extension's passive domain tracker is disabled by default. Enabling it
requires an explicit choice in the extension and lets the user maintain a
domain exclusion list. Manual page, selection and link captures are separate
actions; they enter Browser Inbox and never create a workspace or journal
entry by themselves.
```bash
./build/bin/verstak-desktop --debug
```
## Portable test artifacts
These commands make local alpha artifacts in `release/`. They do not create a
GitHub Release.
```bash
# Debian 13 / Ubuntu 24.04 or later. APT installs WebKitGTK dependencies.
./scripts/package-deb.sh v0.1.0-alpha.1
# Portable Linux x86_64 AppImage with bundled WebKitGTK runtime and plugins.
./scripts/package-appimage.sh v0.1.0-alpha.1
```
Install the Debian package with `sudo apt install ./release/verstak_*.deb`,
then launch `verstak`. Run the AppImage directly after making it executable:
```bash
chmod +x release/verstak-linux-x86_64-v0.1.0-alpha.1.AppImage
./release/verstak-linux-x86_64-v0.1.0-alpha.1.AppImage
```
On distributions without FUSE support, use
`APPIMAGE_EXTRACT_AND_RUN=1 ./release/verstak-linux-x86_64-v0.1.0-alpha.1.AppImage`.
Build the Windows portable ZIP on Linux with MinGW:
```bash
sudo apt install gcc-mingw-w64-x86-64 zip
./scripts/package-windows-portable.sh v0.1.0-alpha.1
```
The ZIP uses the x64 Evergreen Microsoft WebView2 Runtime installed in Windows
and writes `release/verstak-windows-amd64-<version>.zip`. Extract it to a local
disk (not a network share) and launch `Verstak.cmd`. Windows 11 and most
Windows 10 installations already include this runtime. If Verstak does not
start, install the official [Microsoft WebView2 Runtime x64 standalone
installer](https://go.microsoft.com/fwlink/p/?LinkId=2124701), then launch it
again.
Each format is listed in `release/SHA256SUMS` after packaging.
## First local vault
1. Launch the desktop application and choose or create a writable vault folder.
Verstak stores its local metadata in that vault; do not point it at a
read-only directory.
2. Create a Дело (workspace) in the Files plugin before assigning captures or
activity to it.
3. Open Notes, Files, Activity and Journal as needed. Activity candidates are
only suggestions: a Journal entry and a new Дело are always created by the
user.
## Browser extension
The extension is optional. Build it locally with `npm ci && npm test && npm run
build` in `verstak-browser-extension/`, then load `dist/chromium` as an
unpacked Chromium extension or `dist/firefox` temporarily in Firefox. A signed
Firefox XPI is available from the [extension's GitHub
Releases](https://github.com/mirivlad/verstak-browser-extension/releases).
To connect it to the desktop application:
1. Ensure the `browser-inbox` plugin is installed and open its settings in
Verstak.
2. Copy the displayed Receiver URL and Pairing Token.
3. Paste both values into the extension's settings and save.
4. Use a manual Send Page, selection, link or file action to create a Browser
Inbox capture.
Passive domain activity is disabled by default. When the user explicitly turns
it on, the extension sends only bounded time totals by normalized domain. It
does not send URLs, page titles, page contents, keystrokes, navigation history
or inactive-tab time. The extension settings provide a domain exclusion list.
## Optional sync server
The sync server is self-hosted and is not required for local use. Build and
start a development instance from a sibling checkout:
```bash
git clone https://github.com/mirivlad/verstak-sync-server.git verstak-sync-server
cd verstak-sync-server
./scripts/build.sh
./build/bin/verstak-sync-server --port 47732 --data ./server-data \
--admin-user admin --admin-pass 'choose-a-strong-password'
```
For a second device or a production host, follow the deployment, HTTPS and
backup guidance in the [sync server
README](https://github.com/mirivlad/verstak-sync-server#readme). In the desktop
application, open the Sync plugin, enter the server URL and user credentials,
test the connection, then select **Connect**. Each vault is paired separately.
## Separate plugin archives
The desktop artifacts already include matching official plugins. To distribute
plugins separately, build OS-specific archives in the sibling repository:
```bash
cd ../verstak-official-plugins
sudo apt install gcc-mingw-w64-x86-64 zip
./scripts/package-portable.sh v0.1.0-alpha.1
```
It writes a Linux `tar.gz` and a Windows ZIP. Both archives expand directly
into the `plugins/` directory beside the corresponding desktop executable.
Separate archives are required because `platform-test` includes a native
sidecar for its target OS.
## Publish a GitHub Release
```bash
./scripts/publish-github-release.sh v0.1.0-alpha.1
```
The command requires an authenticated [`gh`](https://cli.github.com/) CLI, a
clean local `main` equal to `origin/main`, and the sibling official-plugins
checkout. It builds the Debian package, Linux AppImage and Windows portable
ZIP, then creates or updates the GitHub Release with those three artifacts and
`SHA256SUMS`. Alpha, beta and release-candidate tags are marked as GitHub
prereleases; stable tags are marked latest. The command creates an annotated
tag when needed and pushes it through `origin`, which also mirrors tags in this
checkout. Publish the compatible official-plugins release before the desktop
release that embeds those plugins.
## License
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/hostname-normalization-v1.json",
"$id": "https://raw.githubusercontent.com/mirivlad/verstak-sdk/main/schemas/hostname-normalization-v1.json",
"title": "Verstak canonical hostname normalization v1 test vectors",
"description": "Canonical hostnames are lowercase ASCII A-labels without a port or trailing DNS dot. Bare hostnames accept DNS names, IPv4, bracketed IPv6, localhost, and internal single-label names. URL inputs accept only HTTP(S). Invalid or excessively long input normalizes to an empty string.",
"version": 1,
+3 -2
View File
@@ -243,7 +243,7 @@ func main() {
}
// ─── Wails App ───────────────────────────────────────────
err = wails.Run(&options.App{
appOptions := &options.App{
Title: "Verstak",
Width: 1200,
Height: 800,
@@ -257,7 +257,8 @@ func main() {
Bind: []interface{}{
app,
},
})
}
err = wails.Run(appOptions)
if err != nil {
log.Fatalf("Error: %v", err)
+11
View File
@@ -0,0 +1,11 @@
Package: verstak
Version: @VERSION@
Section: utils
Priority: optional
Architecture: amd64
Depends: libwebkit2gtk-4.1-0, libgtk-3-0t64 | libgtk-3-0
Maintainer: Verstak contributors <dev@verstak.app>
Homepage: https://github.com/mirivlad/verstak
Description: Local-first workspace
Verstak is a local-first workspace for files, notes, browser captures,
activity and work-journal entries.
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env sh
exec /opt/verstak/verstak-desktop "$@"
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
set -eu
HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
export APPDIR="$HERE"
export LD_LIBRARY_PATH="$HERE/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export GIO_EXTRA_MODULES="$HERE/usr/lib/gio/modules"
export GSETTINGS_SCHEMA_DIR="$HERE/usr/share/glib-2.0/schemas"
export GST_PLUGIN_SYSTEM_PATH_1_0="$HERE/usr/lib/gstreamer-1.0"
export WEBKIT_EXEC_PATH="$HERE/usr/lib/webkit2gtk-4.1"
cd "$HERE/usr/bin"
exec "$HERE/usr/bin/verstak-desktop" "$@"
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Verstak
Comment=Local-first workspace
Exec=verstak-desktop %U
Icon=verstak
Terminal=false
Categories=Office;Utility;
StartupNotify=true
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<rect width="128" height="128" rx="26" fill="#1c2f4a"/>
<path d="M28 34h72v18H28zm0 31h72v29H28z" fill="#72d2b3"/>
<path d="M41 52v13m23-13v13m23-13v13" stroke="#1c2f4a" stroke-width="8"/>
</svg>

After

Width:  |  Height:  |  Size: 290 B

+5
View File
@@ -0,0 +1,5 @@
@echo off
setlocal
set "ROOT=%~dp0"
start "" "%ROOT%verstak-desktop.exe" %*
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OFFICIAL_PLUGINS="${VERSTAK_OFFICIAL_PLUGINS_DIR:-$ROOT/../verstak-official-plugins}"
OUTPUT="${VERSTAK_LINUX_BUNDLE_DIR:-$ROOT/build/linux-amd64}"
if [[ ! -d "$OFFICIAL_PLUGINS" ]]; then
echo "official plugins repository not found: $OFFICIAL_PLUGINS" >&2
exit 1
fi
echo "=== verstak desktop Linux amd64 bundle ==="
(cd "$OFFICIAL_PLUGINS" && ./scripts/build.sh)
"$ROOT/scripts/install-dev-plugins.sh"
"$ROOT/scripts/build.sh"
BINARY="$ROOT/build/bin/verstak-desktop"
if [[ ! -x "$BINARY" ]]; then
echo "desktop binary was not produced: $BINARY" >&2
exit 1
fi
rm -rf "$OUTPUT"
mkdir -p "$OUTPUT"
install -m 755 "$BINARY" "$OUTPUT/verstak-desktop"
install -m 644 "$ROOT/README.md" "$ROOT/LICENSE" "$OUTPUT/"
cp -R "$ROOT/plugins" "$OUTPUT/plugins"
chmod -R a+rX "$OUTPUT/plugins"
echo "Linux bundle: $OUTPUT"
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OFFICIAL_PLUGINS="${VERSTAK_OFFICIAL_PLUGINS_DIR:-$ROOT/../verstak-official-plugins}"
WINDOWS_PLUGIN_DIST="${VERSTAK_WINDOWS_PLUGIN_DIST:-$OFFICIAL_PLUGINS/dist-windows}"
WINDOWS_OUTPUT="${VERSTAK_WINDOWS_OUTPUT_DIR:-$ROOT/build/windows-amd64}"
WINDOWS_CC="${VERSTAK_WINDOWS_CC:-x86_64-w64-mingw32-gcc}"
if ! command -v "$WINDOWS_CC" >/dev/null; then
echo "Windows cross-compiler not found: $WINDOWS_CC" >&2
echo "Install x86_64-w64-mingw32-gcc (for example: sudo apt install gcc-mingw-w64-x86-64)." >&2
exit 1
fi
if [[ ! -d "$OFFICIAL_PLUGINS" ]]; then
echo "official plugins repository not found: $OFFICIAL_PLUGINS" >&2
exit 1
fi
WAILS="${WAILS_BIN:-}"
if [[ -z "$WAILS" ]]; then
if command -v wails >/dev/null; then
WAILS="wails"
elif [[ -n "$(go env GOBIN 2>/dev/null)" && -x "$(go env GOBIN)/wails" ]]; then
WAILS="$(go env GOBIN)/wails"
elif [[ -x "$(go env GOPATH)/bin/wails" ]]; then
WAILS="$(go env GOPATH)/bin/wails"
fi
fi
if [[ -z "$WAILS" ]]; then
echo "Wails CLI is required. Install it with: go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0" >&2
exit 1
fi
cd "$ROOT"
echo "=== verstak desktop Windows amd64 build ==="
if [[ ! -d "$ROOT/frontend/node_modules" ]]; then
if [[ -f "$ROOT/frontend/package-lock.json" ]]; then
(cd "$ROOT/frontend" && npm ci --no-audit --no-fund)
else
(cd "$ROOT/frontend" && npm install --no-audit --no-fund)
fi
fi
(cd "$ROOT/frontend" && npm run build)
go mod download
go test -count=1 ./...
"$OFFICIAL_PLUGINS/scripts/build-windows.sh"
if [[ ! -d "$WINDOWS_PLUGIN_DIST" ]]; then
echo "Windows plugin packages were not produced: $WINDOWS_PLUGIN_DIST" >&2
exit 1
fi
# Wails' -compiler option selects a Go binary, not a C compiler. Cross-CGO
# therefore has to be supplied through the standard Go environment instead.
# The portable archive uses the installed Evergreen WebView2 Runtime, so never
# compile an Evergreen downloader into the executable.
CC="$WINDOWS_CC" CGO_ENABLED=1 "$WAILS" build -clean -platform windows/amd64 \
-webview2 error -o verstak-desktop.exe
WINDOWS_BINARY="$ROOT/build/bin/verstak-desktop.exe"
if [[ ! -f "$WINDOWS_BINARY" ]]; then
echo "Windows executable was not produced: $WINDOWS_BINARY" >&2
exit 1
fi
rm -rf "$WINDOWS_OUTPUT"
mkdir -p "$WINDOWS_OUTPUT/plugins"
cp "$WINDOWS_BINARY" "$WINDOWS_OUTPUT/verstak-desktop.exe"
cp -R "$WINDOWS_PLUGIN_DIST/." "$WINDOWS_OUTPUT/plugins/"
echo "Windows test bundle: $WINDOWS_OUTPUT"
echo "Copy this directory to Windows and run verstak-desktop.exe."
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VERSION="${1:-}"
APPIMAGETOOL_URL="${APPIMAGETOOL_URL:-https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage}"
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
echo "usage: $0 <version>" >&2
echo "example: $0 v0.1.0-alpha.1" >&2
exit 2
fi
if ! command -v ldd >/dev/null || ! command -v file >/dev/null; then
echo "ldd and file are required to bundle AppImage runtime libraries" >&2
exit 1
fi
"$ROOT/scripts/build-linux-bundle.sh"
APPIMAGETOOL="${APPIMAGETOOL_BIN:-$ROOT/build/tools/appimagetool-x86_64.AppImage}"
if [[ ! -x "$APPIMAGETOOL" ]]; then
if ! command -v curl >/dev/null; then
echo "appimagetool is missing; install curl or set APPIMAGETOOL_BIN" >&2
exit 1
fi
mkdir -p "$(dirname "$APPIMAGETOOL")"
curl --fail --location --show-error "$APPIMAGETOOL_URL" -o "$APPIMAGETOOL"
chmod +x "$APPIMAGETOOL"
fi
BUNDLE="${VERSTAK_LINUX_BUNDLE_DIR:-$ROOT/build/linux-amd64}"
APPDIR="$ROOT/build/appimage/AppDir"
RELEASE_ROOT="$ROOT/release"
ARCHIVE="$RELEASE_ROOT/verstak-linux-x86_64-$VERSION.AppImage"
WEBKIT_RUNTIME_DIR="${VERSTAK_WEBKIT_RUNTIME_DIR:-/usr/lib/x86_64-linux-gnu/webkit2gtk-4.1}"
if [[ ! -d "$WEBKIT_RUNTIME_DIR" ]] || [[ ! -x "$WEBKIT_RUNTIME_DIR/WebKitWebProcess" ]]; then
echo "WebKitWebProcess was not found at $WEBKIT_RUNTIME_DIR" >&2
echo "Set VERSTAK_WEBKIT_RUNTIME_DIR to the WebKitGTK 4.1 runtime directory." >&2
exit 1
fi
rm -rf "$APPDIR" "$ARCHIVE"
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" "$APPDIR/usr/share/applications" \
"$APPDIR/usr/share/icons/hicolor/scalable/apps"
install -m 755 "$ROOT/packaging/linux/AppRun" "$APPDIR/AppRun"
install -m 755 "$BUNDLE/verstak-desktop" "$APPDIR/usr/bin/verstak-desktop"
install -m 644 "$ROOT/packaging/linux/verstak.desktop" "$APPDIR/verstak.desktop"
install -m 644 "$ROOT/packaging/linux/verstak.desktop" "$APPDIR/usr/share/applications/verstak.desktop"
install -m 644 "$ROOT/packaging/linux/verstak.svg" "$APPDIR/verstak.svg"
install -m 644 "$ROOT/packaging/linux/verstak.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/verstak.svg"
cp -R "$BUNDLE/plugins" "$APPDIR/usr/bin/plugins"
cp -a "$WEBKIT_RUNTIME_DIR" "$APPDIR/usr/lib/webkit2gtk-4.1"
copy_runtime_dir() {
local source="$1"
local target="$2"
if [[ -d "$source" ]]; then
mkdir -p "$target"
cp -a "$source/." "$target/"
fi
}
copy_runtime_dir /usr/lib/x86_64-linux-gnu/gio/modules "$APPDIR/usr/lib/gio/modules"
copy_runtime_dir /usr/lib/x86_64-linux-gnu/gstreamer-1.0 "$APPDIR/usr/lib/gstreamer-1.0"
copy_runtime_dir /usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0 "$APPDIR/usr/lib/gdk-pixbuf-2.0"
copy_runtime_dir /usr/share/glib-2.0/schemas "$APPDIR/usr/share/glib-2.0/schemas"
declare -A queued=()
queue_elf() {
local candidate="$1"
[[ -f "$candidate" ]] || return
if [[ "$(file --brief "$candidate")" != *ELF* ]]; then
return
fi
if [[ -z "${queued[$candidate]:-}" ]]; then
queued[$candidate]=1
printf '%s\n' "$candidate" >> "$APPDIR/.elf-queue"
fi
}
queue_elf "$APPDIR/usr/bin/verstak-desktop"
while IFS= read -r candidate; do
queue_elf "$candidate"
done < <(find "$APPDIR/usr/lib/webkit2gtk-4.1" "$APPDIR/usr/lib/gio/modules" \
"$APPDIR/usr/lib/gstreamer-1.0" "$APPDIR/usr/lib/gdk-pixbuf-2.0" -type f)
while IFS= read -r candidate; do
if ldd "$candidate" | grep -q 'not found'; then
echo "unresolved shared library in $candidate" >&2
exit 1
fi
while IFS= read -r library; do
case "$(basename "$library")" in
libc.so.6|libm.so.6|libpthread.so.0|librt.so.1|libdl.so.2|ld-linux-x86-64.so.2)
continue
;;
esac
bundled="$APPDIR/usr/lib/$(basename "$library")"
if [[ ! -e "$bundled" ]]; then
cp -aL "$library" "$bundled"
fi
queue_elf "$bundled"
done < <(ldd "$candidate" | awk '/=> \/[^ ]+/ { print $3 } /^\// { print $1 }')
done < "$APPDIR/.elf-queue"
rm -f "$APPDIR/.elf-queue"
if command -v glib-compile-schemas >/dev/null; then
glib-compile-schemas "$APPDIR/usr/share/glib-2.0/schemas"
fi
mkdir -p "$RELEASE_ROOT"
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGETOOL" "$APPDIR" "$ARCHIVE"
(cd "$RELEASE_ROOT" && find . -maxdepth 1 -type f \( -name '*.deb' -o -name '*.AppImage' -o -name '*.zip' \) \
-printf '%f\n' | LC_ALL=C sort | xargs -r sha256sum > SHA256SUMS)
echo "AppImage: $ARCHIVE"
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VERSION="${1:-}"
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
echo "usage: $0 <version>" >&2
echo "example: $0 v0.1.0-alpha.1" >&2
exit 2
fi
if ! command -v dpkg-deb >/dev/null; then
echo "dpkg-deb is required to create a Debian package" >&2
exit 1
fi
"$ROOT/scripts/build-linux-bundle.sh"
PACKAGE_VERSION="${VERSION#v}"
BUNDLE="${VERSTAK_LINUX_BUNDLE_DIR:-$ROOT/build/linux-amd64}"
STAGING="$ROOT/build/deb/verstak_$PACKAGE_VERSION"
RELEASE_ROOT="$ROOT/release"
ARCHIVE="$RELEASE_ROOT/verstak_${PACKAGE_VERSION}_amd64.deb"
rm -rf "$STAGING" "$ARCHIVE"
mkdir -p "$STAGING/DEBIAN" "$STAGING/opt/verstak" "$STAGING/usr/bin" \
"$STAGING/usr/share/applications" "$STAGING/usr/share/icons/hicolor/scalable/apps" \
"$STAGING/usr/share/doc/verstak"
sed "s/@VERSION@/$PACKAGE_VERSION/g" "$ROOT/packaging/deb/control" > "$STAGING/DEBIAN/control"
install -m 755 "$ROOT/packaging/deb/verstak" "$STAGING/usr/bin/verstak"
install -m 755 "$BUNDLE/verstak-desktop" "$STAGING/opt/verstak/verstak-desktop"
install -m 644 "$BUNDLE/README.md" "$BUNDLE/LICENSE" "$STAGING/usr/share/doc/verstak/"
install -m 644 "$ROOT/packaging/linux/verstak.desktop" "$STAGING/usr/share/applications/verstak.desktop"
install -m 644 "$ROOT/packaging/linux/verstak.svg" "$STAGING/usr/share/icons/hicolor/scalable/apps/verstak.svg"
cp -R "$BUNDLE/plugins" "$STAGING/opt/verstak/plugins"
mkdir -p "$RELEASE_ROOT"
dpkg-deb --root-owner-group --build "$STAGING" "$ARCHIVE"
(cd "$RELEASE_ROOT" && find . -maxdepth 1 -type f \( -name '*.deb' -o -name '*.AppImage' -o -name '*.zip' \) \
-printf '%f\n' | LC_ALL=C sort | xargs -r sha256sum > SHA256SUMS)
echo "Debian package: $ARCHIVE"
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VERSION="${1:-}"
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
echo "usage: $0 <version>" >&2
echo "example: $0 v0.1.0-alpha.1" >&2
exit 2
fi
for command in zip; do
if ! command -v "$command" >/dev/null; then
echo "$command is required to create the Windows portable archive" >&2
exit 1
fi
done
"$ROOT/scripts/build-windows.sh"
RELEASE_ROOT="$ROOT/release"
STAGING="$RELEASE_ROOT/verstak-windows-amd64-$VERSION"
ARCHIVE="$RELEASE_ROOT/verstak-windows-amd64-$VERSION.zip"
rm -rf "$STAGING" "$ARCHIVE"
mkdir -p "$STAGING"
cp -R "$ROOT/build/windows-amd64/." "$STAGING/"
install -m 644 "$ROOT/README.md" "$ROOT/LICENSE" "$STAGING/"
install -m 644 "$ROOT/packaging/windows/Verstak.cmd" "$STAGING/Verstak.cmd"
(cd "$RELEASE_ROOT" && zip -qr "$(basename "$ARCHIVE")" "$(basename "$STAGING")")
(cd "$RELEASE_ROOT" && find . -maxdepth 1 -type f \( -name '*.deb' -o -name '*.AppImage' -o -name '*.zip' \) \
-printf '%f\n' | LC_ALL=C sort | xargs -r sha256sum > SHA256SUMS)
echo "Windows portable archive: $ARCHIVE"
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VERSION="${1:-}"
REPOSITORY="mirivlad/verstak"
RELEASE_SCRIPT="${VERSTAK_RELEASE_SCRIPT:-$ROOT/scripts/release.sh}"
RELEASE_DIR="${VERSTAK_RELEASE_DIR:-$ROOT/release}"
GIT_BIN="${GIT_BIN:-git}"
GH_BIN="${GH_BIN:-gh}"
cd "$ROOT"
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
echo "usage: $0 <version>" >&2
echo "example: $0 v0.1.0-alpha.1" >&2
exit 2
fi
if ! command -v "$GH_BIN" >/dev/null; then
echo "gh CLI is required to publish a GitHub Release" >&2
exit 1
fi
if [[ "$("$GIT_BIN" branch --show-current)" != "main" ]]; then
echo "GitHub releases must be published from main" >&2
exit 1
fi
if [[ -n "$("$GIT_BIN" status --porcelain)" ]]; then
echo "working tree must be clean before publishing a release" >&2
exit 1
fi
"$GH_BIN" auth status
"$GIT_BIN" fetch origin main --tags
HEAD="$("$GIT_BIN" rev-parse HEAD)"
if [[ "$HEAD" != "$("$GIT_BIN" rev-parse origin/main)" ]]; then
echo "local main must match origin/main before publishing a release" >&2
exit 1
fi
"$RELEASE_SCRIPT" "$VERSION"
ASSETS=(
"$RELEASE_DIR/verstak_${VERSION#v}_amd64.deb"
"$RELEASE_DIR/verstak-linux-x86_64-$VERSION.AppImage"
"$RELEASE_DIR/verstak-windows-amd64-$VERSION.zip"
"$RELEASE_DIR/SHA256SUMS"
)
for asset in "${ASSETS[@]}"; do
if [[ ! -s "$asset" ]]; then
echo "required release asset is missing or empty: $asset" >&2
exit 1
fi
done
if "$GIT_BIN" rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
if [[ "$("$GIT_BIN" rev-parse "${VERSION}^{commit}")" != "$HEAD" ]]; then
echo "existing tag $VERSION does not point at HEAD" >&2
exit 1
fi
else
"$GIT_BIN" tag -a "$VERSION" -m "Release $VERSION"
"$GIT_BIN" push origin "refs/tags/$VERSION"
fi
if "$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" >/dev/null 2>&1; then
"$GH_BIN" release upload "$VERSION" "${ASSETS[@]}" --repo "$REPOSITORY" --clobber
else
RELEASE_OPTIONS=(--generate-notes --verify-tag)
if [[ "$VERSION" == *-* ]]; then
RELEASE_OPTIONS+=(--prerelease)
else
RELEASE_OPTIONS+=(--latest)
fi
"$GH_BIN" release create "$VERSION" "${ASSETS[@]}" \
--repo "$REPOSITORY" \
--title "Verstak $VERSION" \
"${RELEASE_OPTIONS[@]}"
fi
echo "GitHub release:"
"$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" --json url --jq .url
+9 -32
View File
@@ -10,40 +10,17 @@ if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
exit 2
fi
if ! command -v pkg-config >/dev/null || \
(! pkg-config --exists webkit2gtk-4.1 && ! pkg-config --exists webkit2gtk-4.0); then
echo "Linux desktop releases require libwebkit2gtk-4.1-dev or libwebkit2gtk-4.0-dev." >&2
exit 1
fi
OFFICIAL_PLUGINS="${VERSTAK_OFFICIAL_PLUGINS_DIR:-$ROOT/../verstak-official-plugins}"
if [[ ! -d "$OFFICIAL_PLUGINS" ]]; then
echo "official plugins repository not found: $OFFICIAL_PLUGINS" >&2
exit 1
fi
echo "=== verstak desktop release $VERSION ==="
(cd "$OFFICIAL_PLUGINS" && ./scripts/build.sh)
"$ROOT/scripts/install-dev-plugins.sh"
"$ROOT/scripts/build.sh"
BINARY="$(find "$ROOT/build/bin" -maxdepth 1 -type f -name 'verstak-desktop*' -print -quit)"
if [[ -z "$BINARY" ]]; then
echo "desktop binary was not produced in build/bin" >&2
exit 1
fi
RELEASE_ROOT="$ROOT/release"
STAGING="$RELEASE_ROOT/verstak-desktop-$VERSION-linux-amd64"
ARCHIVE="$RELEASE_ROOT/verstak-desktop-linux-amd64-$VERSION.tar.gz"
rm -rf "$STAGING" "$ARCHIVE"
mkdir -p "$STAGING"
rm -rf "$RELEASE_ROOT"
mkdir -p "$RELEASE_ROOT"
cp "$BINARY" "$STAGING/verstak-desktop"
cp "$ROOT/README.md" "$ROOT/LICENSE" "$STAGING/"
cp -R "$ROOT/plugins" "$STAGING/plugins"
tar -C "$RELEASE_ROOT" -czf "$ARCHIVE" "$(basename "$STAGING")"
(cd "$RELEASE_ROOT" && sha256sum "$(basename "$ARCHIVE")" > SHA256SUMS)
"$ROOT/scripts/package-deb.sh" "$VERSION"
"$ROOT/scripts/package-appimage.sh" "$VERSION"
"$ROOT/scripts/package-windows-portable.sh" "$VERSION"
echo "release archive: $ARCHIVE"
(cd "$RELEASE_ROOT" && find . -maxdepth 1 -type f \( -name '*.deb' -o -name '*.AppImage' -o -name '*.zip' \) \
-printf '%f\n' | LC_ALL=C sort | xargs -r sha256sum > SHA256SUMS)
echo "release assets: $RELEASE_ROOT"
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUILDER="$ROOT/scripts/build-windows.sh"
if [[ ! -x "$BUILDER" ]]; then
echo "Windows desktop builder is missing or not executable: $BUILDER" >&2
exit 1
fi
bash -n "$BUILDER"
grep -Fq -- '-platform windows/amd64' "$BUILDER"
grep -Fq 'CC="$WINDOWS_CC"' "$BUILDER"
grep -Fq 'CGO_ENABLED=1' "$BUILDER"
grep -Fq -- '-webview2 error' "$BUILDER"
grep -Fq 'dist-windows' "$BUILDER"
grep -Fq 'verstak-desktop.exe' "$BUILDER"
grep -Fq 'x86_64-w64-mingw32-gcc' "$BUILDER"
echo "Windows desktop build script contract passed"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
for script in build-linux-bundle.sh package-deb.sh package-appimage.sh package-windows-portable.sh; do
path="$ROOT/scripts/$script"
if [[ ! -x "$path" ]]; then
echo "packaging script is missing or not executable: $path" >&2
exit 1
fi
bash -n "$path"
done
grep -Fq 'dpkg-deb' "$ROOT/scripts/package-deb.sh"
grep -Fq -- '--build' "$ROOT/scripts/package-deb.sh"
grep -Fq 'packaging/deb/control' "$ROOT/scripts/package-deb.sh"
grep -Fq 'libwebkit2gtk-4.1-0' "$ROOT/packaging/deb/control"
grep -Fq 'appimagetool' "$ROOT/scripts/package-appimage.sh"
grep -Fq 'WebKitWebProcess' "$ROOT/scripts/package-appimage.sh"
if grep -Fq 'FixedVersionRuntime' "$ROOT/scripts/package-windows-portable.sh"; then
echo "Windows portable archive must not bundle Fixed Version WebView2" >&2
exit 1
fi
if grep -Fq 'msedgewebview2.exe' "$ROOT/scripts/package-windows-portable.sh"; then
echo "Windows portable archive must not bundle WebView2 binaries" >&2
exit 1
fi
if [[ -e "$ROOT/platform_options_windows.go" ]]; then
echo "Windows runtime override must not be present" >&2
exit 1
fi
grep -Fq 'zip -qr' "$ROOT/scripts/package-windows-portable.sh"
grep -Fq 'LinkId=2124701' "$ROOT/README.md"
grep -Fq 'WebView2 Runtime' "$ROOT/README.md"
grep -Fq 'package-deb.sh' "$ROOT/scripts/release.sh"
grep -Fq 'package-appimage.sh' "$ROOT/scripts/release.sh"
grep -Fq 'package-windows-portable.sh' "$ROOT/scripts/release.sh"
grep -Fq 'chmod -R a+rX' "$ROOT/scripts/build-linux-bundle.sh"
echo "desktop package script contracts passed"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PUBLISHER="$ROOT/scripts/publish-github-release.sh"
VERSION="v0.0.0-test"
REPOSITORY="mirivlad/verstak"
ASSET_NAMES=(
"verstak_0.0.0-test_amd64.deb"
"verstak-linux-x86_64-v0.0.0-test.AppImage"
"verstak-windows-amd64-v0.0.0-test.zip"
)
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
if [[ ! -x "$PUBLISHER" ]]; then
echo "publisher is missing or not executable: $PUBLISHER" >&2
exit 1
fi
mkdir -p "$WORK/bin" "$WORK/release" "$WORK/state"
LOG="$WORK/log"
export LOG
cat > "$WORK/release.sh" <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
printf 'release:%s\n' "$1" >> "$LOG"
printf 'deb\n' > "$VERSTAK_RELEASE_DIR/verstak_${1#v}_amd64.deb"
printf 'appimage\n' > "$VERSTAK_RELEASE_DIR/verstak-linux-x86_64-$1.AppImage"
printf 'zip\n' > "$VERSTAK_RELEASE_DIR/verstak-windows-amd64-$1.zip"
printf 'checksum\n' > "$VERSTAK_RELEASE_DIR/SHA256SUMS"
SCRIPT
chmod +x "$WORK/release.sh"
cat > "$WORK/bin/git" <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
if [[ "$PWD" != "${EXPECTED_ROOT:?}" ]]; then
echo "publisher did not enter repository root: $PWD" >&2
exit 1
fi
case "${1:-}" in
status) exit 0 ;;
branch) echo main ;;
fetch) printf 'fetch\n' >> "$LOG" ;;
rev-parse)
if [[ "${2:-}" == "-q" ]]; then
if [[ -f "$TEST_STATE/tag" ]]; then echo test-commit; exit 0; fi
exit 1
fi
echo test-commit
;;
tag) touch "$TEST_STATE/tag"; printf 'tag:%s\n' "${3:-}" >> "$LOG" ;;
push) printf 'push:%s:%s\n' "${2:-}" "${3:-}" >> "$LOG" ;;
*) echo "unexpected git invocation: $*" >&2; exit 1 ;;
esac
SCRIPT
chmod +x "$WORK/bin/git"
cat > "$WORK/bin/gh" <<'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
case "${1:-}:${2:-}" in
auth:status) printf 'auth\n' >> "$LOG" ;;
release:view)
if [[ -f "$TEST_STATE/release" ]]; then echo https://github.example/release; else exit 1; fi
;;
release:create) touch "$TEST_STATE/release"; printf 'create:%s\n' "$*" >> "$LOG" ;;
release:upload) printf 'upload:%s\n' "$*" >> "$LOG" ;;
*) echo "unexpected gh invocation: $*" >&2; exit 1 ;;
esac
SCRIPT
chmod +x "$WORK/bin/gh"
run_publisher() {
VERSTAK_RELEASE_SCRIPT="$WORK/release.sh" \
VERSTAK_RELEASE_DIR="$WORK/release" \
GIT_BIN="$WORK/bin/git" \
GH_BIN="$WORK/bin/gh" \
EXPECTED_ROOT="$ROOT" \
TEST_STATE="$WORK/state" \
"$PUBLISHER" "$VERSION"
}
run_publisher
grep -Fqx "release:$VERSION" "$LOG"
grep -Fqx "tag:$VERSION" "$LOG"
grep -Fqx "push:origin:refs/tags/$VERSION" "$LOG"
grep -F "release create $VERSION" "$LOG" >/dev/null
for asset in "${ASSET_NAMES[@]}"; do
grep -F "$asset" "$LOG" >/dev/null
done
grep -F "SHA256SUMS" "$LOG" >/dev/null
grep -F -- "--prerelease" "$LOG" >/dev/null
run_publisher
grep -F "release upload $VERSION" "$LOG" >/dev/null
echo "GitHub release publisher test passed"