- one version: package.json's version is the desktop app's too (desktopVersion removed); tools/desktop-version.ts keeps the plugin manifests and the app's copies equal, desktop-release publish refuses another version, release order app first then npm publish; the unpublished desktop 0.1.0 candidate is noted in the docs and CHANGELOG - storebound desktop installs (per user, no admin: macOS DMG -> /Applications or ~/Applications, Windows NSIS /S, Linux AppImage + .desktop with the storebound:// handler), updates an older app, notes a newer one (downgrade only with --force), asks once ([Y/n], --yes, exit 3 without a terminal), opens on the folder; --dry / --json / --download; the install steps are pure plans (cli/lib/desktop-install.ts); the version's own manifest dl/<version>/latest.json - credentials: one store, the CLI's credential profiles in ~/.storebound/credentials; the desktop writes and reads them (profiles.rs, files 0600), moves old keychain accounts into profiles on first start, shows profiles the CLI made; precedence env > named profile > project file > default profile; config `account`, "account" in the project's credentials.json; storebound accounts list | import (dedup by key id / service account e-mail, key files copied 0600, project file keeps keystore and review contact, backup, --dry, never prints values) | use - app folders: one list ~/.storebound/projects.json shared by the desktop (registry.rs), storebound desktop, apps folders | add | remove, ws init and ws without a workspace file - desktop: notice when a folder pins another storebound version; storebound-desktop --version - tests: version drift, desktop decisions and per-OS plans, credential precedence and import, registry (TS + Rust), keychain migration and profile round trip (Rust), boot check with old keychain state and the new shared state
83 lines
4.6 KiB
Bash
Executable file
83 lines
4.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Root-owned publish step for storebound desktop releases (install as /usr/local/sbin/storebound-desktop-publish,
|
|
# root:root 755; see docs/desktop-release.md). tools/desktop-release.ts publish uploads one version folder to
|
|
# /var/lib/storebound-desktop/incoming/<version>/ and then runs: storebound-desktop-publish <version> [stable|beta]
|
|
#
|
|
# It checks the upload (regular files with plain names only, SHA256SUMS, latest.json of that version whose every URL
|
|
# points at an uploaded file of /desktop/dl/<version>/), copies the files into /var/www/storebound-desktop/dl/<version>/
|
|
# (a new version: a temp folder renamed into place; an existing version: only new files, a file already published is
|
|
# never replaced by different bytes), writes the version's own dl/<version>/latest.json (what `storebound desktop` of
|
|
# the same CLI version installs), and only then swaps latest.json of the channel atomically (temp file + rename).
|
|
# Apache serves that folder under https://storebound.dev/desktop/ (tools/vds/apache-storebound-desktop.conf), apart
|
|
# from the site's own rsync, which deletes what is not in the repository. Nothing from the upload is executed.
|
|
set -euo pipefail
|
|
umask 022
|
|
|
|
VERSION="${1:-}"
|
|
CHANNEL="${2:-stable}"
|
|
INCOMING=/var/lib/storebound-desktop/incoming
|
|
ROOT=/var/www/storebound-desktop
|
|
SITE=https://storebound.dev/desktop
|
|
|
|
die() { echo "storebound-desktop-publish: $*" >&2; exit 1; }
|
|
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] || die "usage: storebound-desktop-publish <version> [stable|beta]"
|
|
[[ "$CHANNEL" == stable || "$CHANNEL" == beta ]] || die "channel: stable or beta"
|
|
IN="$INCOMING/$VERSION"
|
|
DL="$ROOT/dl/$VERSION"
|
|
[ -d "$IN" ] && [ ! -L "$IN" ] || die "nothing uploaded at $IN"
|
|
|
|
# only regular files with plain names, nothing hidden, no folders, no links
|
|
if find "$IN" -mindepth 1 \( ! -type f -o -name '.*' \) -print | grep -q .; then die "the upload holds something other than plain files"; fi
|
|
if find "$IN" -mindepth 1 -type f -printf '%f\n' | grep -Ev '^[A-Za-z0-9][A-Za-z0-9._-]*$' | grep -q .; then die "a file name has unexpected characters"; fi
|
|
[ -f "$IN/latest.json" ] && [ -f "$IN/SHA256SUMS" ] || die "latest.json and SHA256SUMS are required"
|
|
(cd "$IN" && sha256sum --quiet --strict -c SHA256SUMS) || die "SHA256SUMS does not match the files"
|
|
|
|
python3 - "$IN" "$VERSION" "$SITE" <<'PY' || die "latest.json does not describe this upload"
|
|
import json, os, re, sys
|
|
src, version, site = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
m = json.load(open(os.path.join(src, "latest.json")))
|
|
assert m.get("version") == version, "version"
|
|
files = set(os.listdir(src))
|
|
listed = {l.split()[1] for l in open(os.path.join(src, "SHA256SUMS")) if l.strip()}
|
|
assert files - {"SHA256SUMS"} <= listed, "files missing from SHA256SUMS"
|
|
prefix = f"{site}/dl/{version}/"
|
|
entries = list((m.get("platforms") or {}).values()) + list((m.get("downloads") or {}).values())
|
|
assert entries, "no platforms"
|
|
for e in entries:
|
|
url = e.get("url", "")
|
|
assert url.startswith(prefix), url
|
|
name = url[len(prefix):]
|
|
assert re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name) and name in files, url
|
|
assert e.get("signature"), url
|
|
PY
|
|
|
|
install -d -m 755 "$ROOT" "$ROOT/dl" "$ROOT/beta"
|
|
if [ ! -e "$DL" ]; then
|
|
TMP="$(mktemp -d "$ROOT/dl/.tmp-$VERSION.XXXXXX")"
|
|
for f in "$IN"/*; do n="$(basename "$f")"; [ "$n" = latest.json ] || install -m 644 "$f" "$TMP/$n"; done
|
|
chmod 755 "$TMP"
|
|
mv -T "$TMP" "$DL"
|
|
else
|
|
for f in "$IN"/*; do
|
|
n="$(basename "$f")"
|
|
[ "$n" = latest.json ] && continue
|
|
if [ -e "$DL/$n" ]; then
|
|
[ "$n" = SHA256SUMS ] && continue
|
|
cmp -s "$f" "$DL/$n" || die "$n is already published with other bytes: a published file is never replaced (bump the version)"
|
|
else
|
|
install -m 644 "$f" "$DL/.$n.tmp" && mv -f "$DL/.$n.tmp" "$DL/$n"
|
|
fi
|
|
done
|
|
install -m 644 "$IN/SHA256SUMS" "$DL/.SHA256SUMS.tmp" && mv -f "$DL/.SHA256SUMS.tmp" "$DL/SHA256SUMS"
|
|
fi
|
|
|
|
# the version's own manifest: dl/<version>/latest.json. `storebound desktop` of CLI <version> installs exactly that
|
|
# version from it (the CLI and the app share one version number); it may be updated when installers are added
|
|
install -m 644 "$IN/latest.json" "$DL/.latest.json.tmp" && mv -f "$DL/.latest.json.tmp" "$DL/latest.json"
|
|
|
|
# latest.json last: installed apps see the new version only once every file it names is in place
|
|
if [ "$CHANNEL" = stable ]; then LJ="$ROOT/latest.json"; URL="$SITE/latest.json"; else LJ="$ROOT/beta/latest.json"; URL="$SITE/beta/latest.json"; fi
|
|
install -m 644 "$IN/latest.json" "$LJ.tmp"
|
|
mv -f "$LJ.tmp" "$LJ"
|
|
rm -rf "$IN"
|
|
curl -fsS -o /dev/null --resolve storebound.dev:443:127.0.0.1 "$URL" && echo "storebound desktop $VERSION published ($CHANNEL): $URL"
|