From c5ce7239f54d609a9ce8437178d58ff005192d7f Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Sun, 27 Sep 2026 14:34:19 +0100 Subject: [PATCH 1/2] docs(site): install the current release from the public channel The installation page advertised terraphim-ai GitHub assets pinned at 1.20.5, a Homebrew formula that does not exist, and a Cargo package name that is not published. Following any of them failed. * quick install, Homebrew and Cargo commands corrected; the tap carries terraphim-agent and terraphim-grep, so the fabricated terraphim-ai formula is gone * per-platform download links point at downloads.terraphim.ai and carry the version, matching how the release is actually published * versions advanced from 1.20.5/1.21.9 to 1.21.16, including config.toml so the homepage version selector and release links agree * server and library bindings moved to their own section, since they are not published through the client installer * troubleshooting documents the installer's exit codes, including the checksum-mismatch code, and no longer suggests skip-verify as a remedy Verified: zola build succeeds; all seven generated download URLs match the live stable-v2.json manifest; no stale brew or cargo command remains in content/ or themes/. --- config.toml | 4 +- content/capabilities/terraphim-agent.md | 2 +- content/docs/crates.md | 4 +- content/docs/installation.md | 136 +++++++++++++++--------- content/docs/quickstart.md | 2 +- content/posts/learning-via-negativa.md | 2 +- content/posts/native-hook-support.md | 4 +- content/releases.md | 46 ++++---- themes/WarpDrive/templates/index.html | 32 +++--- 9 files changed, 136 insertions(+), 96 deletions(-) diff --git a/config.toml b/config.toml index 79ca95b..32b9092 100644 --- a/config.toml +++ b/config.toml @@ -50,8 +50,8 @@ include_content = true [languages] [extra] -version = "1.20.5" -release_version = "1.20.5" +version = "1.21.16" +release_version = "1.21.16" navbar_items = [ { code = "en", nav_items = [ diff --git a/content/capabilities/terraphim-agent.md b/content/capabilities/terraphim-agent.md index 5f94429..cd48e4f 100644 --- a/content/capabilities/terraphim-agent.md +++ b/content/capabilities/terraphim-agent.md @@ -20,7 +20,7 @@ you encounter a recurring error. Install it with one command: ```bash -cargo install terraphim-agent +cargo install terraphim_agent --features repl-full ``` ## What it does diff --git a/content/docs/crates.md b/content/docs/crates.md index d997964..07f13c9 100644 --- a/content/docs/crates.md +++ b/content/docs/crates.md @@ -144,10 +144,10 @@ Cross-language bindings for using Terraphim from Python, Node.js, and WebAssembl ```bash # Install the agent (interactive REPL + session search) -cargo install terraphim-agent +cargo install terraphim_agent --features repl-full # Install the CLI (JSON output for automation) -cargo install terraphim-cli +cargo install terraphim_cli ``` Or use the universal installer: diff --git a/content/docs/installation.md b/content/docs/installation.md index 5e9a7f9..9e7f4fe 100644 --- a/content/docs/installation.md +++ b/content/docs/installation.md @@ -10,38 +10,46 @@ Choose the installation method that best suits your needs and platform. ## Quick Install (Recommended) -The universal installer automatically detects your platform and installs the appropriate version. +The universal installer detects your platform, resolves the current release +from the release channel, verifies the download's SHA-256, and unpacks it into +`~/.local/bin`. ```bash curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash ``` -## Package Managers - -### Homebrew (macOS/Linux) +Options: ```bash -brew tap terraphim/terraphim && brew install terraphim-ai +# Also install the CLI and grep tools +curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash --with-cli --with-grep + +# Install somewhere else +curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash --install-dir /usr/local/bin + +# Require a specific version (fails if the channel serves a different one) +curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash --version 1.21.16 ``` -This installs `terraphim-agent` and `terraphim_server`. +The installer publishes these exit codes: `0` success, `1` usage error, +`2` manifest unreachable, `3` requested version unavailable, `4` download +failed, `5` checksum mismatch, `6` installation failed. -### Cargo (Rust) +## Package Managers -Install using Cargo, Rust's package manager. +### Homebrew (macOS/Linux) ```bash -# Install agent with interactive REPL and full features -cargo install terraphim_agent --features repl-full +brew tap terraphim/terraphim && brew install terraphim-agent ``` -### Debian/Ubuntu +The tap also carries `terraphim-grep`. There is no `terraphim-ai` formula. -Download the `.deb` package from the latest release: +### Cargo (Rust) ```bash -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-server_1.20.5-1_amd64.deb -sudo dpkg -i terraphim-server_1.20.5-1_amd64.deb +# Install agent with interactive REPL and full features +cargo install terraphim_agent --features repl-full ``` ## Platform-Specific Guides @@ -50,33 +58,41 @@ sudo dpkg -i terraphim-server_1.20.5-1_amd64.deb #### Binary Download -Download the latest release from GitHub: +Archives are served from `downloads.terraphim.ai`. Substitute the release you +want for `1.21.16`, or read the current version from the +[manifest](https://downloads.terraphim.ai/terraphim-agent/stable-v2.json). ```bash +VERSION=1.21.16 + # x86_64 (GNU) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-x86_64-unknown-linux-gnu.tar.gz -tar -xzf terraphim-agent-1.20.5-x86_64-unknown-linux-gnu.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" +tar -xzf "terraphim-agent-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" sudo mv terraphim-agent /usr/local/bin/ # x86_64 (MUSL / static) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-x86_64-unknown-linux-musl.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-x86_64-unknown-linux-musl.tar.gz" # ARM64 (MUSL) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-aarch64-unknown-linux-musl.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-aarch64-unknown-linux-musl.tar.gz" +``` +Verify the download against the digest in the manifest before installing: + +```bash +curl -fsSL https://downloads.terraphim.ai/terraphim-agent/stable-v2.json \ + | python3 -c 'import json,sys; m=json.load(sys.stdin); print(m["version"]); print(m["assets"]["x86_64-unknown-linux-gnu"]["sha256"])' +sha256sum "terraphim-agent-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" ``` #### Build from Source ```bash -# Clone the repository -git clone https://github.com/terraphim/terraphim-ai.git -cd terraphim-ai +git clone https://github.com/terraphim/terraphim-clients.git +cd terraphim-clients -# Build all binaries -cargo build --release +cargo build --release -p terraphim_agent --bin terraphim-agent -# Install sudo cp target/release/terraphim-agent /usr/local/bin/ ``` @@ -85,26 +101,31 @@ sudo cp target/release/terraphim-agent /usr/local/bin/ #### Binary Download ```bash +VERSION=1.21.16 + # Apple Silicon (ARM64) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-aarch64-apple-darwin.tar.gz -tar -xzf terraphim-agent-1.20.5-aarch64-apple-darwin.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-aarch64-apple-darwin.tar.gz" +tar -xzf "terraphim-agent-${VERSION}-aarch64-apple-darwin.tar.gz" sudo mv terraphim-agent /usr/local/bin/ # Intel (x86_64) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-x86_64-apple-darwin.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-x86_64-apple-darwin.tar.gz" -# Universal (Fat binary) -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-universal-apple-darwin.tar.gz +# Universal (runs on both) +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-${VERSION}-universal-apple-darwin.tar.gz" ``` +macOS builds are signed and notarised by Apple; the self-updater verifies an +Ed25519 signature over every archive before installing it. + #### Build from Source Requires Xcode command line tools. ```bash -git clone https://github.com/terraphim/terraphim-ai.git -cd terraphim-ai -cargo build --release +git clone https://github.com/terraphim/terraphim-clients.git +cd terraphim-clients +cargo build --release -p terraphim_agent --bin terraphim-agent sudo cp target/release/terraphim-agent /usr/local/bin/ ``` @@ -113,26 +134,35 @@ sudo cp target/release/terraphim-agent /usr/local/bin/ #### Binary Download ```powershell -# Download and extract -curl -LO https://github.com/terraphim/terraphim-ai/releases/latest/download/terraphim-agent-1.20.5-x86_64-pc-windows-msvc.zip +$VERSION = "1.21.16" +curl.exe -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-$VERSION-x86_64-pc-windows-msvc.zip" ``` Extract the zip and add the directory to your PATH. -- [Download for Windows x64](https://github.com/terraphim/terraphim-ai/releases/latest) - #### Build from Source Requires [Rust for Windows](https://rustup.rs/). ```powershell -git clone https://github.com/terraphim/terraphim-ai.git -cd terraphim-ai -cargo build --release -# Binaries will be in target\release\ +git clone https://github.com/terraphim/terraphim-clients.git +cd terraphim-clients +cargo build --release -p terraphim_agent --bin terraphim-agent +# Binary will be in target\release\ ``` -## Library Bindings +## Server and Library Bindings + +The commands above install the client tools (`terraphim-agent`, +`terraphim-grep`, `terraphim-cli`). The server and the language bindings have +their own release paths: + +### Terraphim server + +The server is built and released from +[terraphim-ai](https://github.com/terraphim/terraphim-ai). Build it from +source, or use the container images published to the GitHub Container +Registry. ### npm (Node.js / Bun) @@ -175,7 +205,10 @@ After installation, verify that Terraphim is working: ```bash # Check version terraphim-agent --version -# terraphim-agent 1.20.5 +# terraphim-agent 1.21.16 + +# Confirm the updater can see the channel +terraphim-agent check-update # Start the REPL terraphim-agent repl @@ -193,24 +226,31 @@ chmod +x /usr/local/bin/terraphim-agent ### Command Not Found -Ensure that the installation directory is in your PATH: +Ensure that the installation directory is in your PATH. The universal +installer adds `~/.local/bin` to your shell profile: ```bash # For bash -echo 'export PATH=$PATH:/usr/local/bin' >> ~/.bashrc +echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.bashrc source ~/.bashrc # For zsh -echo 'export PATH=$PATH:/usr/local/bin' >> ~/.zshrc +echo 'export PATH=$PATH:$HOME/.local/bin' >> ~/.zshrc source ~/.zshrc ``` +### Checksum Mismatch + +The installer exits with code `5` if the downloaded archive does not match the +digest in the release manifest. Re-run the install; if it fails again, please +open an issue rather than using `--skip-verify`. + ### Rust Version Issues Ensure that you have a recent Rust version: ```bash -rustc --version # Should be 1.75.0 or later +rustc --version # Should be 1.85.0 or later rustup update stable ``` @@ -219,4 +259,4 @@ rustup update stable - [Quickstart Guide](/docs/quickstart) — Get up and running in 5 minutes - [Configuration Guide](/docs/terraphim_config) — Customise Terraphim to your needs - [Discord Community](https://discord.gg/VPJXB6BGuY) — Join our Discord for support -- [Discourse Forum](https://terraphim.discourse.group) — Community discussions and Q&A +- [Discourse Forum](https://terraphim.discourse.group) — Community discussions and Q&A \ No newline at end of file diff --git a/content/docs/quickstart.md b/content/docs/quickstart.md index ff308ae..b4c8ed7 100644 --- a/content/docs/quickstart.md +++ b/content/docs/quickstart.md @@ -25,7 +25,7 @@ curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts ### Option B: Homebrew (macOS/Linux) ```bash -brew tap terraphim/terraphim && brew install terraphim-ai +brew tap terraphim/terraphim && brew install terraphim-agent ``` ### Option C: Cargo diff --git a/content/posts/learning-via-negativa.md b/content/posts/learning-via-negativa.md index c6784e7..98a8afc 100644 --- a/content/posts/learning-via-negativa.md +++ b/content/posts/learning-via-negativa.md @@ -217,7 +217,7 @@ You typed `git push -f` in a repo with protected branches? It learns that `-f` i ```bash # Install terraphim-agent -cargo install terraphim-agent +cargo install terraphim_agent --features repl-full # Install the learning hook for Claude Code terraphim-agent learn install-hook claude diff --git a/content/posts/native-hook-support.md b/content/posts/native-hook-support.md index 610ee75..9e3553e 100644 --- a/content/posts/native-hook-support.md +++ b/content/posts/native-hook-support.md @@ -159,7 +159,7 @@ Each role learns differently and optimises search for its domain. ```bash # Install latest terraphim-agent -cargo install terraphim-agent +cargo install terraphim_agent --features repl-full # Install hook for your AI agent terraphim-agent learn install-hook claude @@ -189,7 +189,7 @@ This release passed rigorous quality gates: ```bash # Install -cargo install terraphim-agent +cargo install terraphim_agent --features repl-full # Set up your role terraphim-agent setup --template rust-engineer-v2 diff --git a/content/releases.md b/content/releases.md index 53ffb4f..99213eb 100644 --- a/content/releases.md +++ b/content/releases.md @@ -10,11 +10,11 @@ paginate_by = 10 Stay up-to-date with the latest Terraphim AI releases. -## Latest Release: v1.21.9 +## Latest Release: v1.21.16 -**Released:** 6 July 2026 +**Released:** 25 September 2026 -[GitHub Releases](https://github.com/terraphim/terraphim-clients/releases/tag/v1.21.9) | [Release Notes](https://github.com/terraphim/terraphim-clients/releases/tag/v1.21.9) +[GitHub Releases](https://github.com/terraphim/terraphim-clients/releases/tag/v1.21.16) | [Release Notes](https://github.com/terraphim/terraphim-clients/releases/tag/v1.21.16) ### Self-Update (recommended) @@ -37,21 +37,25 @@ curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts Binaries are distributed via Cloudflare R2 with zero-egress CDN. Browse the manifests: -- [terraphim-agent manifest](https://downloads.terraphim.ai/terraphim-agent/stable.json) -- [terraphim-grep manifest](https://downloads.terraphim.ai/terraphim-grep/stable.json) -- [terraphim-cli manifest](https://downloads.terraphim.ai/terraphim-cli/stable.json) +- [terraphim-agent manifest](https://downloads.terraphim.ai/terraphim-agent/stable-v2.json) +- [terraphim-grep manifest](https://downloads.terraphim.ai/terraphim-grep/stable-v2.json) +- [terraphim-cli manifest](https://downloads.terraphim.ai/terraphim-cli/stable-v2.json) + +The `stable-v2.json` manifests carry the version, release date and a SHA-256 +digest plus size for every published archive. The older `stable.json` pointers +are still served for pre-1.21.15 clients. Download the latest archive for your platform directly: ```bash -curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-1.21.9-x86_64-unknown-linux-gnu.tar.gz" -tar -xzf terraphim-agent-1.21.9-*.tar.gz +curl -fsSLO "https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-1.21.16-x86_64-unknown-linux-gnu.tar.gz" +tar -xzf terraphim-agent-1.21.16-*.tar.gz sudo mv terraphim-agent /usr/local/bin/ ``` ### Available Binaries -v1.21.9 ships across three client tools: +v1.21.16 ships across three client tools: - **`terraphim-agent`** — full CLI + REPL + TUI - **`terraphim-grep`** — intelligent hybrid search with RLM fallback @@ -73,21 +77,17 @@ All Linux and macOS archives are **Ed25519-signed** and verified on install by t ### What's New -**R2 binary distribution — no rate limits, no GitHub token** -- Client binaries served from Cloudflare R2 via `downloads.terraphim.ai` (free global CDN egress) -- JSON manifest backend — version discovery is a single HTTP GET (sub-second, edge-cached) -- `terraphim-agent update` and `terraphim-grep update` work out of the box with no credentials -- GitHub Releases retained as an automatic fallback +**Public install path corrected** +- The universal installer now resolves release versions from the channel manifests rather than from version-less GitHub assets, so it installs the current release instead of an older one +- Downloads are verified against the manifest's SHA-256 before anything is unpacked, and a mismatch aborts the install +- Archives are unpacked from a staging directory; the destination is only written once the bytes are verified and the expected binary is present -**Archive signing (Ed25519 / zipsign)** -- Every `.tar.gz` archive is now signed and verified on install -- Multi-key verifier supports key rotation (2026-07 clients key + 2025-01 legacy key) -- Unsigned archives are rejected — `MissingSignature` is a hard failure, not a warning +**Version reporting and exit codes** +- The installer reports a specific exit code per failure (unreachable manifest, unavailable version, failed download, checksum mismatch) -**Self-update robustness** -- Install-path fix: updates now install to the running binary's location (no more `~/.cargo/bin` shadowing `/usr/local/bin`) -- Atomic-rename install: can replace the currently-running binary without `ETXTBSY` -- Backend selector: `TERRAPHIM_UPDATE_BACKEND=r2|github` env override +**Release integrity** +- `stable-v2.json` manifests publish a SHA-256 digest and byte size for every archive +- Archive signing (Ed25519 / zipsign) continues to apply to every `.tar.gz` ### Installation @@ -128,7 +128,7 @@ View complete release history on [GitHub Releases (terraphim-clients)](https://g Stable releases are recommended for production use. They are thoroughly tested and signed. The self-updater fetches signed archives from `downloads.terraphim.ai` by default. -**Latest Stable:** v1.21.9 +**Latest Stable:** v1.21.16 ### Development diff --git a/themes/WarpDrive/templates/index.html b/themes/WarpDrive/templates/index.html index fcd28b5..fe4cac7 100644 --- a/themes/WarpDrive/templates/index.html +++ b/themes/WarpDrive/templates/index.html @@ -22,8 +22,8 @@

Install terraphim-agent
$ - cargo install terraphim-agent - + cargo install terraphim_agent --features repl-full +
or Homebrew, curl, binary downloads @@ -35,9 +35,9 @@

Quickstart - + - v{{ config.extra.release_version | default(value="1.20.5") }} release + v{{ config.extra.release_version | default(value="1.21.16") }} release @@ -710,7 +710,7 @@

Edge & Embedded

-
+
- {% set rv = config.extra.release_version | default(value="1.20.5") %} - {% set dl = "https://github.com/terraphim/terraphim-ai/releases/download/v" ~ rv ~ "/" %} + {% set rv = config.extra.release_version | default(value="1.21.16") %} + {% set dl = "https://downloads.terraphim.ai/terraphim-agent/" %}
All platforms
    @@ -778,8 +778,8 @@

    Homebrew

    $ - brew tap terraphim/terraphim && brew install terraphim-ai - + brew tap terraphim/terraphim && brew install terraphim-agent +
@@ -792,13 +792,13 @@

Cargo

$ - cargo install terraphim-agent - + cargo install terraphim_agent --features repl-full +
$ - cargo install terraphim-cli - + cargo install terraphim_cli +

@@ -814,7 +814,7 @@

Windows

curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash -

Requires WSL. Or download the .zip directly.

+

Requires WSL. Or download the .zip directly.

@@ -872,9 +872,9 @@

Browser Extension

- + - Download v{{ config.extra.release_version | default(value="1.20.5") }} + Download v{{ config.extra.release_version | default(value="1.21.16") }} From 18f63cc3692b17705df6590c65ec19511552ec7a Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Sun, 27 Sep 2026 14:34:19 +0100 Subject: [PATCH 2/2] ci(site): replace the dead Netlify gate with Cloudflare Pages validation The site is served from Cloudflare Pages (deploy.yml), but pull requests were still judged by a Netlify integration that reports failure on every commit, including already-merged PRs #15 and #18. Those four checks were the only checks on PR #19, so every pull request looked broken. Changes: - validate.yml: a pull-request gate that builds the site with the same pinned Zola, verifies the build produced pages, and checks the release channel. - check-release-links.py: confirms the version the site advertises is the version downloads.terraphim.ai serves, and that every asset the site links to resolves with a non-trivial size. - deploy.yml: pin the Zola download with --fail, serialise production deploys with a concurrency group, and verify the deployed site actually serves the release version committed to the repository before the job reports success. Verified locally: `zola build` produces 272 HTML pages; the channel checker fails on current main (advertises 1.20.5, channel serves 1.21.16) and passes on the docs/install-current-release content (1.21.16, all targets resolve). --- .github/workflows/deploy.yml | 53 +++++++++++-- .github/workflows/validate.yml | 67 +++++++++++++++++ scripts/check-release-links.py | 132 +++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 scripts/check-release-links.py diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index dca9751..879fa32 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,12 +1,28 @@ name: Deploy to Cloudflare Pages +# Production deploy. Runs after a push to main (or on demand). +# +# The Cloudflare Pages project `terraphim-ai` is deployed exclusively through +# this workflow. There is no separate CDN/Git integration to keep in sync, so a +# green run here means the published site matches main. + on: push: branches: - main workflow_dispatch: +permissions: + contents: read + deployments: write + +concurrency: + group: cloudflare-pages-production + cancel-in-progress: false + env: + ZOLA_VERSION: "0.22.1" + CF_PROJECT: terraphim-ai OP_API_TOKEN: op://TerraphimPlatform/terraphim-md-book-cloudflare/workers-api-token OP_ACCOUNT_ID: op://TerraphimPlatform/terraphim-md-book-cloudflare/account_id @@ -14,17 +30,17 @@ jobs: build-and-deploy: name: Build and Deploy runs-on: ubuntu-latest - permissions: - contents: read - deployments: write steps: - name: Checkout repository uses: actions/checkout@v6 - - name: Install Zola 0.22.1 + - name: Install Zola ${{ env.ZOLA_VERSION }} run: | - curl -L https://github.com/getzola/zola/releases/download/v0.22.1/zola-v0.22.1-x86_64-unknown-linux-gnu.tar.gz | tar xz + curl --fail --location --silent --show-error \ + "https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \ + | tar xz sudo mv zola /usr/local/bin + zola --version - name: Build site run: zola build @@ -45,4 +61,29 @@ jobs: with: apiToken: ${{ env.CLOUDFLARE_API_TOKEN }} accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy public --project-name=terraphim-ai --branch=main + command: pages deploy public --project-name=${{ env.CF_PROJECT }} --branch=main + + - name: Verify the deployed site serves the committed version + env: + DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }} + run: | + expected=$(grep -oP '^release_version\s*=\s*"\K[^"]+' config.toml) + url="${DEPLOY_URL:-https://${CF_PROJECT}.pages.dev}" + echo "Expecting release_version ${expected} at ${url}" + + attempt=1 + while [ "${attempt}" -le 5 ]; do + body=$(curl --fail --location --silent --show-error --max-time 30 "${url}/" || true) + if printf '%s' "${body}" | grep -q "${expected}"; then + echo "Deployment served release ${expected} on attempt ${attempt}" + exit 0 + fi + echo "Attempt ${attempt}: ${expected} not found yet, retrying" + attempt=$((attempt + 1)) + sleep 15 + done + + # Surface the site content for debugging before failing. + curl --fail --location --silent --show-error --max-time 30 "${url}/" | head -c 2000 || true + echo "::error::Deployed site does not advertise release ${expected}" + exit 1 \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..0215369 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,67 @@ +name: Validate site + +# The site deploys to Cloudflare Pages from main only (.github/workflows/deploy.yml). +# This workflow is the pull-request gate: it proves the site builds, that every +# internal link resolves, and that the download URLs the site advertises match the +# public release channel. Without it a broken build would only surface at deploy +# time, after the merge. + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +env: + ZOLA_VERSION: "0.22.1" + +jobs: + build: + name: Build and check links + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install Zola ${{ env.ZOLA_VERSION }} + run: | + curl --fail --location --silent --show-error \ + "https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \ + | tar xz + sudo mv zola /usr/local/bin + zola --version + + - name: Build site + # Zola fails the build on broken internal links. + run: zola build + + - name: Verify the build produced pages + run: | + pages=$(find public -name '*.html' | wc -l) + echo "Generated ${pages} HTML pages" + if [ "${pages}" -lt 10 ]; then + echo "::error::Expected at least 10 generated pages, found ${pages}" + exit 1 + fi + test -f public/index.html || { echo "::error::public/index.html is missing"; exit 1; } + + - name: Upload site + uses: actions/upload-artifact@v4 + with: + name: site-public + path: public/ + retention-days: 7 + + release-links: + name: Check release channel links + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Verify advertised downloads exist on the public channel + run: python3 scripts/check-release-links.py \ No newline at end of file diff --git a/scripts/check-release-links.py b/scripts/check-release-links.py new file mode 100644 index 0000000..32652ea --- /dev/null +++ b/scripts/check-release-links.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Verify that the release download URLs advertised by the site resolve. + +The site publishes download links against the public release channel +(downloads.terraphim.ai). This check reads the version the site advertises, then +confirms the channel manifests serve that version and that every asset the site +links to is present on the channel with a non-trivial size. + +It is the release half of CI: build validation proves the site compiles, this +proves the site does not advertise downloads that do not exist. + +The channel is fronted by Cloudflare bot management, which rejects generic +clients, so requests carry a descriptive User-Agent. + +Exit codes: + 0 every advertised URL resolved + 1 an advertised URL is missing or unusable + 2 the channel could not be reached +""" + +from __future__ import annotations + +import json +import os +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path + +CHANNEL_BASE = os.environ.get("TERRAPHIM_CHANNEL_BASE", "https://downloads.terraphim.ai") +USER_AGENT = "terraphim-site-release-check/1.0 (release validation)" + +# Targets the site offers downloads for, and how the site names them. +TARGETS = { + "x86_64-unknown-linux-gnu": "linux-x86_64", + "aarch64-unknown-linux-musl": "linux-aarch64", + "x86_64-apple-darwin": "macos-x86_64", + "aarch64-apple-darwin": "macos-aarch64", + "x86_64-pc-windows-msvc": "windows-x86_64", +} + +CLI_BINARIES = ("terraphim-agent", "terraphim-cli", "terraphim-grep") +MIN_ASSET_BYTES = 100_000 + +ROOT = Path(__file__).resolve().parent.parent +CONFIG = ROOT / "config.toml" + + +def read_site_version() -> str: + """Return the release version the site advertises in config.toml.""" + text = CONFIG.read_text(encoding="utf-8") + match = re.search(r'^release_version\s*=\s*"([^"]+)"', text, re.MULTILINE) + if not match: + print("::error::config.toml has no extra.release_version") + sys.exit(1) + return match.group(1) + + +def fetch(url: str, limit: int = 64 * 1024 * 1024) -> bytes: + request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(request, timeout=60) as response: + return response.read(limit) + + +def head_size(url: str) -> int | None: + """Return Content-Length for a URL, or None when unavailable.""" + request = urllib.request.Request(url, method="HEAD", headers={"User-Agent": USER_AGENT}) + try: + with urllib.request.urlopen(request, timeout=60) as response: + length = response.headers.get("Content-Length") + return int(length) if length else None + except urllib.error.HTTPError: + return None + + +def manifest_for(binary: str) -> dict: + url = f"{CHANNEL_BASE}/{binary}/stable-v2.json" + try: + return json.loads(fetch(url)) + except urllib.error.HTTPError as error: + print(f"::error::manifest {url} returned HTTP {error.code}") + sys.exit(2) + except (urllib.error.URLError, ValueError, TimeoutError) as error: + print(f"::error::manifest {url} unreachable: {error}") + sys.exit(2) + + +def main() -> int: + version = read_site_version() + print(f"Site advertises release {version}") + + failures: list[str] = [] + + for binary in CLI_BINARIES: + manifest = manifest_for(binary) + channel_version = manifest.get("version", "") + if channel_version != version: + failures.append( + f"{binary}: channel serves {channel_version!r} but the site advertises {version!r}" + ) + continue + + assets = manifest.get("assets", {}) + for target, label in TARGETS.items(): + asset = assets.get(target) + if not asset: + failures.append(f"{binary}: channel manifest has no asset for {target}") + continue + + # The manifest path is relative to the channel root and already + # includes the binary name, e.g. "terraphim-agent/terraphim-agent-...". + url = f"{CHANNEL_BASE}/{asset['path'].lstrip('/')}" + size = head_size(url) + if size is None: + failures.append(f"{binary} {label}: {url} did not resolve") + elif size < MIN_ASSET_BYTES: + failures.append(f"{binary} {label}: {url} is only {size} bytes") + + print(f"{binary}: checked {len(TARGETS)} advertised targets against {CHANNEL_BASE}") + + if failures: + for failure in failures: + print(f"::error::{failure}") + return 1 + + print(f"All advertised downloads for release {version} resolve on {CHANNEL_BASE}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file