releases shell, synced 2026-10-03

This commit is contained in:
openswarm
2026-10-03 13:57:45 -07:00
commit e00c2eb35d
5 changed files with 238 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
name: Promotion gate (update feeds agree)
# The "don't let a half-baked release become latest" gate. Releases should be
# cut as DRAFT first (publish.sh drafts experimental builds; do the same for
# stable and only un-draft after dogfooding — see docs/RELEASE_CHECKLIST.md).
# When a release is published / un-drafted, this verifies BOTH auto-updater
# feeds exist, agree on version (with each other and the tag), and that every
# referenced asset actually resolves (HEAD 200). If a platform's feed is
# missing or versions mismatch, this goes red so the bad release is caught
# before users auto-update into it.
on:
release:
types: [published, released, prereleased]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to verify (e.g. v1.2.3)'
required: true
permissions:
contents: read
jobs:
verify-feeds:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.18.1'
- name: Resolve tag
id: tag
shell: bash
run: |
tag="${{ github.event.release.tag_name }}"
[ -z "$tag" ] && tag="${{ github.event.inputs.tag }}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "ver=${tag#v}" >> "$GITHUB_OUTPUT"
- name: Download release feeds
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
mkdir -p feeds
gh release download "${{ steps.tag.outputs.tag }}" --repo "${{ github.repository }}" \
-p 'latest*.yml' -D feeds || true
ls -la feeds
- name: Verify both feeds exist, agree, and resolve
shell: bash
run: |
node scripts/release/verify-release.js \
--dir feeds \
--expect-version "${{ steps.tag.outputs.ver }}" \
--base-url "https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}"
+45
View File
@@ -0,0 +1,45 @@
OpenSwarm Proprietary Software Licence
Copyright (c) 2026 Open Swarm. All rights reserved.
This software and its source code are proprietary and confidential. They are made
available only to people expressly authorised by Open Swarm, and only for the purpose
Open Swarm authorised.
You may not copy, modify, merge, publish, distribute, sublicense, sell, or create
derivative works from this software or its source code, in whole or in part, and you
may not disclose it to anyone outside your authorisation, without prior written
permission from Open Swarm.
You may not reverse engineer, decompile, disassemble, or decrypt any part of this
software, or otherwise attempt to derive its source code, structure, or algorithms from
the compiled application, and you may not remove, bypass, or defeat any measure that
protects it, except to the extent applicable law expressly permits this despite this
restriction.
You may not use this software, its source code, or any part or derivative of either,
for any commercial purpose, including to build, offer, or train any product or service,
without prior written permission from Open Swarm.
These restrictions apply to the source code and to the compiled OpenSwarm application
alike.
Use of the compiled OpenSwarm application is also governed by the terms of service and
any end user licence agreement published at https://openswarm.com. Those terms add to
this file; they do not relax the restrictions above unless they say so expressly.
Third-party components distributed with this software remain under their own licences,
which are unaffected by this file.
Earlier public releases: versions of this software published before 8 September 2026
were released under the GNU Affero General Public License version 3. Those releases,
and the source published with them, remain available under that licence, and nothing in
this file withdraws, narrows, or otherwise affects the rights any person already
received under it. This licence governs this version and later ones only.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# OpenSwarm
OpenSwarm has moved in a new direction, and its development no longer happens here. This repository now holds only the signed builds: every installed copy checks here for updates, and the download links on [openswarm.com](https://openswarm.com) point here.
## Download
- macOS (Apple silicon): https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-arm64.dmg
- macOS (Intel): https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-x64.dmg
- Windows: https://github.com/openswarm-ai/openswarm/releases/latest/download/OpenSwarm-Setup-x64.exe
The release marked **Latest** is the stable build. Releases marked **Pre-release** are the experimental channel, which the app offers when you turn on experimental updates in Settings.
## Bugs and requests
Open an issue here: https://github.com/openswarm-ai/openswarm/issues/new
## Licence
OpenSwarm is proprietary software. See [LICENSE](LICENSE), and the terms of service at [openswarm.com](https://openswarm.com).
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env node
// Phase 5a promotion gate: before a draft release is allowed to become "latest",
// prove both auto-updater feeds exist, agree on version, and that their assets
// actually resolve. A release that ships latest.yml but not latest-mac.yml (or
// with mismatched versions) silently strands one platform's users on the old
// build, which is the exact "broken latest" failure this gate exists to stop.
//
// Usage:
// node scripts/release/verify-release.js --dir <artifacts-dir> --expect-version 1.2.3
// node scripts/release/verify-release.js --dir <dir> --expect-version 1.2.3 \
// --base-url https://github.com/openswarm-ai/openswarm/releases/download/v1.2.3
//
// --dir directory containing latest.yml + latest-mac.yml
// --expect-version version both feeds (and their filenames) must match
// --base-url if given, HEAD-check every referenced asset resolves (200)
//
// Exit 0 = promotable. Exit 1 = blocked (prints the first blocking reason).
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const FEEDS = ['latest.yml', 'latest-mac.yml'];
function parseArgs(argv) {
const out = { dir: null, expectVersion: null, baseUrl: null, json: false };
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--dir') out.dir = argv[++i];
else if (argv[i] === '--expect-version') out.expectVersion = argv[++i];
else if (argv[i] === '--base-url') out.baseUrl = argv[++i];
else if (argv[i] === '--json') out.json = true;
}
return out;
}
// Minimal electron-builder-feed parser. We only need `version:` and the asset
// filenames (top-level `path:` plus each `- url:` under `files:`). Avoiding a
// YAML dependency keeps this runnable on a bare CI node with no install step.
function parseFeed(text) {
const version = (text.match(/^version:\s*(.+)$/m) || [])[1];
const assets = new Set();
const topPath = (text.match(/^path:\s*(.+)$/m) || [])[1];
if (topPath) assets.add(topPath.trim());
const urlRe = /^\s*-?\s*url:\s*(.+)$/gm;
let m;
while ((m = urlRe.exec(text)) !== null) assets.add(m[1].trim());
return { version: version ? version.trim() : null, assets: [...assets] };
}
function headOk(url) {
return new Promise((resolve) => {
const req = https.request(url, { method: 'HEAD' }, (res) => {
// GitHub release assets 302 to a signed CDN URL; follow one hop.
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
headOk(res.headers.location).then(resolve);
} else {
resolve(res.statusCode === 200);
}
});
req.on('error', () => resolve(false));
req.setTimeout(15000, () => { req.destroy(); resolve(false); });
req.end();
});
}
function fail(msg, json) {
if (json) process.stdout.write(JSON.stringify({ ok: false, error: msg }) + '\n');
else process.stderr.write(`BLOCKED: ${msg}\n`);
process.exit(1);
}
async function main() {
const args = parseArgs(process.argv.slice(2));
if (!args.dir) fail('--dir <artifacts-dir> is required', args.json);
const feeds = {};
for (const name of FEEDS) {
const p = path.join(args.dir, name);
if (!fs.existsSync(p)) fail(`missing feed: ${name} (one platform would be stranded on the old build)`, args.json);
feeds[name] = parseFeed(fs.readFileSync(p, 'utf8'));
if (!feeds[name].version) fail(`${name} has no version: field`, args.json);
}
const versions = FEEDS.map((n) => feeds[n].version);
if (new Set(versions).size !== 1) {
fail(`version mismatch across feeds: ${FEEDS.map((n) => `${n}=${feeds[n].version}`).join(', ')}`, args.json);
}
const releaseVersion = versions[0];
if (args.expectVersion) {
const want = args.expectVersion.replace(/^v/, '');
if (releaseVersion !== want) fail(`feeds say ${releaseVersion} but expected ${want}`, args.json);
}
if (args.baseUrl) {
const base = args.baseUrl.replace(/\/+$/, '');
for (const name of FEEDS) {
// The .yml itself must resolve, plus every asset it points at.
const toCheck = [name, ...feeds[name].assets];
for (const asset of toCheck) {
const url = `${base}/${asset}`;
// eslint-disable-next-line no-await-in-loop
const ok = await headOk(url);
if (!ok) fail(`asset does not resolve (HEAD != 200): ${url}`, args.json);
}
}
}
const result = { ok: true, version: releaseVersion, feeds: FEEDS, checkedUrls: !!args.baseUrl };
if (args.json) process.stdout.write(JSON.stringify(result) + '\n');
else {
process.stdout.write(`\nPROMOTABLE: both feeds present, version ${releaseVersion} agrees`);
process.stdout.write(args.baseUrl ? ', all assets resolve.\n\n' : ' (URL check skipped; pass --base-url to enable).\n\n');
}
}
main();