Skip to content

Commit c4af43c

Browse files
echobtcursoragent
andauthored
feat(release): auto-publish cli builds to cloudflare r2 (#5)
Wire release.yml to call publish-r2.yml after a successful tag or dispatch release. Upload archives, checksums, release JSON, and checksum-verifying installers to software.cortex.foundation. Teach cortex upgrade to parse the static R2 manifest layout. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Mathis <echobt@users.noreply.github.com>
1 parent be70d96 commit c4af43c

18 files changed

Lines changed: 955 additions & 238 deletions

File tree

.github/workflows/publish-r2.yml

Lines changed: 196 additions & 139 deletions
Large diffs are not rendered by default.

.github/workflows/release.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,24 @@ jobs:
374374
env:
375375
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
376376

377+
# ==========================================================================
378+
# Publish binaries to software.cortex.foundation (Cloudflare R2)
379+
# ==========================================================================
380+
publish:
381+
name: Publish to software.cortex.foundation
382+
needs: [prepare, release]
383+
if: success() && needs.release.result == 'success'
384+
uses: ./.github/workflows/publish-r2.yml
385+
with:
386+
version: ${{ needs.prepare.outputs.version }}
387+
channel: ${{ contains(needs.prepare.outputs.version, '-') && 'beta' || 'stable' }}
388+
release_run_id: ${{ github.run_id }}
389+
secrets:
390+
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
391+
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
392+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
393+
permissions:
394+
contents: read
395+
actions: read
396+
377397

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,22 @@ in your OS keyring.
4949

5050
### Linux and macOS
5151

52+
The installer downloads the matching archive from
53+
[software.cortex.foundation](https://software.cortex.foundation), verifies its
54+
SHA-256, and installs to `~/.local/bin`. Review it first if you prefer:
55+
5256
```bash
57+
curl -fsSL https://software.cortex.foundation/install.sh | less
5358
curl -fsSL https://software.cortex.foundation/install.sh | sh
5459
```
5560

61+
Update an existing install with `cortex upgrade`. Pin a version with
62+
`CORTEX_VERSION=0.1.4` when running the installer.
63+
5664
### Windows
5765

66+
Windows installs into `%LOCALAPPDATA%\Cortex\bin` after the same checksum check:
67+
5868
```powershell
5969
irm https://software.cortex.foundation/install.ps1 | iex
6070
```
@@ -171,7 +181,8 @@ through `generate_tui_demo` and rasterises the frames into `docs/media/intro.gif
171181

172182
Merges to `main` run [`.github/workflows/version-bump.yml`](.github/workflows/version-bump.yml),
173183
which patch-bumps the version and tags it. Tags run
174-
[`release.yml`](.github/workflows/release.yml), which can publish to
184+
[`release.yml`](.github/workflows/release.yml), which builds GitHub Release
185+
assets and **automatically** publishes them to Cloudflare R2 /
175186
[software.cortex.foundation](https://software.cortex.foundation) via
176187
[`publish-r2.yml`](.github/workflows/publish-r2.yml).
177188

docs/CI_SECRETS.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# CI secrets
22

3-
This repository does not invent cloud accounts. The workflows imported from `CortexLM/cortex-code` already point at `cortex.foundation` / `software.cortex.foundation`. Configure these GitHub Actions secrets on `CortexLM/cli` only if the matching account already exists.
3+
This repository does not invent cloud accounts. Configure these GitHub Actions secrets and variables on `CortexLM/cli` only if the matching Cortex Foundation account already exists.
44

5-
None of these values belong in git.
5+
None of these values belong in git. Do not add AWS access keys or an IAM user for CLI binary publishing.
66

77
## Required for PR CI (`.github/workflows/ci.yml`)
88

@@ -16,21 +16,42 @@ No secrets. `fmt`, `clippy`, `test`, `audit`, and TUI jobs use the public crates
1616

1717
## Release artifacts (`.github/workflows/release.yml`)
1818

19-
Builds `Cortex` binaries and attaches them to the GitHub Release. Uses `GITHUB_TOKEN` only.
19+
Fires on tag `v*` / `cli-v*` and on `workflow_dispatch` with a version. Builds `Cortex` binaries, creates a GitHub Release, then **automatically** calls `.github/workflows/publish-r2.yml` (`workflow_call`) so the same artifacts are published to `https://software.cortex.foundation`.
2020

21-
Optional Linux deps (`libasound2-dev`) are installed in the job; no extra secrets.
21+
The GitHub Release job uses `GITHUB_TOKEN` only. Optional Linux deps (`libasound2-dev`) are installed in the build job; no extra secrets.
2222

2323
## Publish to software.cortex.foundation (`.github/workflows/publish-r2.yml`)
2424

25-
The existing R2 publisher uploads to bucket `cortex-software` and public URLs under `https://software.cortex.foundation/v1/assets/...`.
25+
Production publisher. Uploads to Cloudflare R2 bucket `cortex-software` using **R2 API tokens** (not AWS IAM, not GitHub OIDC). Public URLs stay under `https://software.cortex.foundation`.
26+
27+
Called automatically after a successful `release` job. Can also be run by hand (`workflow_dispatch`) to republish, passing the release workflow's `release_run_id`.
2628

2729
| Secret | Used for |
2830
|--------|----------|
29-
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key (already used by cortex-code) |
31+
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key for bucket `cortex-software` |
3032
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret |
31-
| `CLOUDFLARE_ACCOUNT_ID` | R2 endpoint `https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com` |
33+
| `CLOUDFLARE_ACCOUNT_ID` | R2 S3 endpoint `https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com` |
34+
35+
No GitHub Actions variables are required for this path. Do not configure `github-production-deploy`, `PRODUCTION_SOFTWARE_BUCKET`, or AWS keys for CLI binaries.
36+
37+
### Public URL map
38+
39+
Bucket root is the host root (`software.cortex.foundation`):
40+
41+
| Object | Public URL |
42+
|--------|------------|
43+
| Unix installer | `https://software.cortex.foundation/install.sh` |
44+
| Windows installer | `https://software.cortex.foundation/install.ps1` |
45+
| Channel manifest | `https://software.cortex.foundation/releases/manifest.json` |
46+
| Per-version JSON | `https://software.cortex.foundation/releases/<version>.json` |
47+
| Platform archive | `https://software.cortex.foundation/v1/assets/<platform>/<version>/cortex.tar.gz` (`.zip` on Windows) |
48+
| Archive checksum | same path + `.sha256` |
49+
50+
Aliases also written for `cortex upgrade`: `/v1/releases/manifest.json`, `/v1/releases/<version>.json`, `/v1/releases/latest.json`.
51+
52+
Platform keys: `linux-x86_64`, `linux-aarch64`, `darwin-x86_64`, `darwin-aarch64`, `windows-x86_64`.
3253

33-
Do not add AWS account IDs, IAM users, or a second bucket name unless Cortex Foundation already operates them.
54+
`cortex upgrade` and the installers parse the same `ReleaseInfo` / `manifest.json` JSON. Tag versions that contain `-` (prerelease) publish to the `beta` channel; otherwise `stable`.
3455

3556
## Homebrew / WinGet (optional)
3657

docs/guides/getting-started.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ This page takes you from nothing installed to a working Cortex Code session.
77
### Linux and macOS
88

99
The install script fetches the release build for your platform from
10-
[software.cortex.foundation](https://software.cortex.foundation):
10+
[software.cortex.foundation](https://software.cortex.foundation), verifies the
11+
SHA-256 checksum from the release manifest, and installs into `~/.local/bin`
12+
(`Cortex`, plus a `cortex` symlink). Add that directory to `PATH` if it is not
13+
already there.
1114

1215
```bash
1316
curl -fsSL https://software.cortex.foundation/install.sh | sh
@@ -19,8 +22,14 @@ Read it first if you would rather not pipe a script into a shell:
1922
curl -fsSL https://software.cortex.foundation/install.sh | less
2023
```
2124

25+
Pin a version with `CORTEX_VERSION=0.1.4`. Update later with `cortex upgrade`
26+
(same host, same checksum).
27+
2228
### Windows
2329

30+
Windows installs into `%LOCALAPPDATA%\Cortex\bin` and adds that folder to the
31+
user `PATH`:
32+
2433
```powershell
2534
irm https://software.cortex.foundation/install.ps1 | iex
2635
```
@@ -50,6 +59,17 @@ cargo build -p cortex-cli --release
5059
cortex --version
5160
```
5261

62+
### Update
63+
64+
```bash
65+
cortex upgrade # latest on the stable channel
66+
cortex upgrade --check # report only
67+
```
68+
69+
`cortex upgrade` talks to `https://software.cortex.foundation`
70+
(`/releases/manifest.json` and `/v1/assets/...`), verifies SHA-256, and
71+
replaces the current binary.
72+
5373
## 2. Sign in
5474

5575
```bash

scripts/install.ps1

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Install Cortex CLI from https://software.cortex.foundation
2+
#
3+
# Usage:
4+
# irm https://software.cortex.foundation/install.ps1 | iex
5+
#
6+
# Optional environment:
7+
# CORTEX_VERSION Pin a version (e.g. 0.1.2). Default: latest on the channel.
8+
# CORTEX_CHANNEL stable (default), beta, or nightly
9+
# CORTEX_INSTALL_DIR Prefix (default: $env:LOCALAPPDATA\Cortex). Binary in PREFIX\bin.
10+
# CORTEX_SOFTWARE_URL Override the distribution host (testing only).
11+
#
12+
# Downloads the matching zip, verifies SHA-256, then installs Cortex.exe.
13+
# Checksum verification is required; the script will not install an unverified file.
14+
15+
Set-StrictMode -Version Latest
16+
$ErrorActionPreference = "Stop"
17+
18+
$SoftwareUrl = if ($env:CORTEX_SOFTWARE_URL) { $env:CORTEX_SOFTWARE_URL.TrimEnd("/") } else { "https://software.cortex.foundation" }
19+
$Channel = if ($env:CORTEX_CHANNEL) { $env:CORTEX_CHANNEL } else { "stable" }
20+
$Prefix = if ($env:CORTEX_INSTALL_DIR) { $env:CORTEX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Cortex" }
21+
$BinDir = Join-Path $Prefix "bin"
22+
$PinnedVersion = if ($env:CORTEX_VERSION) { $env:CORTEX_VERSION.TrimStart("v") } else { $null }
23+
24+
if ($Channel -notin @("stable", "beta", "nightly")) {
25+
throw "install.ps1: invalid CORTEX_CHANNEL='$Channel' (use stable, beta, or nightly)"
26+
}
27+
28+
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
29+
switch ($arch) {
30+
"X64" { $Platform = "windows-x86_64" }
31+
"Arm64" { throw "install.ps1: Windows ARM64 builds are not published yet. Use an x64 machine or the GitHub Release." }
32+
default { throw "install.ps1: unsupported architecture: $arch" }
33+
}
34+
35+
function Get-Json($Url) {
36+
return Invoke-RestMethod -Uri $Url -Method Get
37+
}
38+
39+
function Try-GetJson($Url) {
40+
try {
41+
return Invoke-RestMethod -Uri $Url -Method Get
42+
} catch {
43+
return $null
44+
}
45+
}
46+
47+
Write-Host "Cortex CLI installer"
48+
Write-Host " host: $SoftwareUrl"
49+
Write-Host " platform: $Platform"
50+
Write-Host " prefix: $Prefix"
51+
52+
$release = $null
53+
$version = $PinnedVersion
54+
55+
if ($version) {
56+
$release = Try-GetJson "$SoftwareUrl/releases/$version.json"
57+
if (-not $release) {
58+
$release = Try-GetJson "$SoftwareUrl/v1/releases/$version.json"
59+
}
60+
if (-not $release) {
61+
throw "install.ps1: could not fetch release metadata for $version from $SoftwareUrl"
62+
}
63+
} else {
64+
$manifest = Try-GetJson "$SoftwareUrl/releases/manifest.json"
65+
if (-not $manifest) {
66+
$manifest = Try-GetJson "$SoftwareUrl/v1/releases/manifest.json"
67+
}
68+
if (-not $manifest) {
69+
throw "install.ps1: could not fetch $SoftwareUrl/releases/manifest.json"
70+
}
71+
$release = $manifest.$Channel
72+
if (-not $release) {
73+
throw "install.ps1: no $Channel release in manifest"
74+
}
75+
$version = $release.version
76+
}
77+
78+
$asset = $release.assets.$Platform
79+
if (-not $asset) {
80+
throw "install.ps1: no asset for platform $Platform in version $version"
81+
}
82+
83+
$expectedSha = ([string]$asset.sha256).Trim().ToLowerInvariant()
84+
if (-not $expectedSha) {
85+
throw "install.ps1: release JSON missing sha256 for $Platform"
86+
}
87+
88+
Write-Host " version: $version"
89+
Write-Host " download: $($asset.url)"
90+
91+
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("cortex-install-" + [guid]::NewGuid().ToString("N"))
92+
New-Item -ItemType Directory -Path $tempRoot | Out-Null
93+
try {
94+
$zipPath = Join-Path $tempRoot "cortex.zip"
95+
Invoke-WebRequest -Uri $asset.url -OutFile $zipPath -UseBasicParsing
96+
97+
$actualSha = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
98+
if ($actualSha -ne $expectedSha) {
99+
throw "install.ps1: SHA-256 mismatch for cortex.zip: expected $expectedSha, got $actualSha"
100+
}
101+
Write-Host " checksum: ok"
102+
103+
$extractDir = Join-Path $tempRoot "extract"
104+
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
105+
106+
$binary = Get-ChildItem -Path $extractDir -Recurse -File |
107+
Where-Object { $_.Name -in @("Cortex.exe", "cortex.exe") } |
108+
Select-Object -First 1
109+
if (-not $binary) {
110+
throw "install.ps1: archive did not contain Cortex.exe"
111+
}
112+
113+
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
114+
$dest = Join-Path $BinDir "Cortex.exe"
115+
Copy-Item -Path $binary.FullName -Destination $dest -Force
116+
117+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
118+
$pathParts = @()
119+
if ($userPath) {
120+
$pathParts = $userPath.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)
121+
}
122+
if ($pathParts -notcontains $BinDir) {
123+
$newPath = if ($userPath) { "$userPath;$BinDir" } else { $BinDir }
124+
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
125+
$env:Path = "$env:Path;$BinDir"
126+
Write-Host "Added $BinDir to the user PATH."
127+
}
128+
129+
Write-Host "Installed Cortex CLI v$version to $dest"
130+
Write-Host "Restart the terminal, then run: cortex --version"
131+
} finally {
132+
Remove-Item -Recurse -Force $tempRoot -ErrorAction SilentlyContinue
133+
}

0 commit comments

Comments
 (0)