commit 490852037dc9b766798c0b31df2fcf5ced0c1db0 Author: openswarm Date: Sat Oct 3 14:23:30 2026 -0700 releases shell, synced 2026-10-03 diff --git a/.github/workflows/promotion-gate.yml b/.github/workflows/promotion-gate.yml new file mode 100644 index 00000000..8a94f4b3 --- /dev/null +++ b/.github/workflows/promotion-gate.yml @@ -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 }}" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a49f8e70 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..fbd46ff8 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# OpenSwarm + +OpenSwarm has moved in a new direction, and its development no longer happens here. This repository now holds only the signed builds, which every installed copy checks for updates. + +## 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). diff --git a/electron/build/icon.ico b/electron/build/icon.ico new file mode 100644 index 00000000..18621b6e Binary files /dev/null and b/electron/build/icon.ico differ diff --git a/scripts/release/verify-release.js b/scripts/release/verify-release.js new file mode 100644 index 00000000..e8291610 --- /dev/null +++ b/scripts/release/verify-release.js @@ -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 --expect-version 1.2.3 +// node scripts/release/verify-release.js --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 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();