Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions .github/actions/check-latest-release/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
# SPDX-License-Identifier: Apache-2.0

name: Check Latest Release
description: Check current remote stable tags before updating a shared latest channel
description: Check current remote release tags before updating a shared latest channel

inputs:
release-tag:
description: Stable version being published
description: Stable or prerelease version being published
required: true

outputs:
make-latest:
description: Whether this is still the highest stable version in the repository
description: Whether this is still the highest release version in the repository
value: ${{ steps.latest.outputs.make-latest }}

runs:
Expand All @@ -24,23 +24,33 @@ runs:
with:
script: |
const tag = process.env.RELEASE_TAG;
const parse = value => /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/.exec(value)?.slice(1).map(BigInt);
const includePrereleases = tag.includes('-pre.');
const parse = value => {
const match = /^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-pre\.([1-9][0-9]*))?$/.exec(value);
if (!match) return null;
return [BigInt(match[1]), BigInt(match[2]), BigInt(match[3]), match[4] ? 0n : 1n, BigInt(match[4] || 0)];
};
const compare = (left, right) => {
for (let i = 0; i < left.length; i++) {
if (left[i] !== right[i]) return left[i] < right[i] ? -1 : 1;
}
return 0;
};
const version = parse(tag);
if (!version) {
throw new Error(`Invalid stable release tag: ${tag}`);
throw new Error(`Invalid release tag: ${tag}`);
}
const tags = await github.paginate(github.rest.repos.listTags, {
...context.repo,
per_page: 100,
});
if (!tags.some(candidate => candidate.name === tag)) {
throw new Error(`Stable release tag ${tag} is missing from the repository`);
throw new Error(`Release tag ${tag} is missing from the repository`);
}
const newer = tags.find(candidate => {
if (!includePrereleases && candidate.name.includes('-pre.')) return false;
const other = parse(candidate.name);
if (!other) return false;
const index = other.findIndex((part, i) => part !== version[i]);
return index !== -1 && other[index] > version[index];
return other && compare(other, version) > 0;
});
core.info(newer ? `${tag} is superseded by ${newer.name}; preserving latest` : `${tag} is the latest stable version`);
core.info(newer ? `${tag} is superseded by ${newer.name}; preserving latest` : `${tag} is the latest release version`);
core.setOutput('make-latest', !newer);
2 changes: 2 additions & 0 deletions .github/workflows/release-prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ jobs:
python-version: ${{ needs.build.outputs.python_version }}
npm-version: ${{ needs.build.outputs.npm_version }}
is-prerelease: true
secrets:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}

qualify:
name: Qualify
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,18 @@ jobs:
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}

publish-prerelease-snap:
name: Publish Prerelease Snap
needs: release
if: inputs.is-prerelease
uses: ./.github/workflows/snap-publish.yml
with:
upload-channel: latest/beta
release-tag: ${{ inputs.release-tag }}
github-environment: latest/beta
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}

publish-sdk-typescript:
name: Publish TypeScript SDK
needs: release
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/snap-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
environment: ${{ inputs.github-environment }}
concurrency:
group: snap-publish-${{ inputs.upload-channel }}-${{ matrix.arch }}
cancel-in-progress: false
queue: max

Check failure on line 47 in .github/workflows/snap-publish.yml

View workflow job for this annotation

GitHub Actions / Actionlint (informational)

unexpected key "queue" for "concurrency" section. expected one of "cancel-in-progress", "group"
Comment thread
pimlock marked this conversation as resolved.
Dismissed
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: inputs.release-tag != ''
Expand Down Expand Up @@ -90,6 +94,9 @@
snap_name="${snap_name%%_*}"
component_args=()
for comp in "${snap_name}"+*.comp; do
component_args+=(--component "$comp")
component_file="${comp##*/}"
component_name="${component_file#${snap_name}+}"
component_name="${component_name%%_*}"
component_args+=(--component "${component_name}=${comp}")
done
snapcraft upload --release "${INPUTS_UPLOAD_CHANNEL}" "$snap_file" "${component_args[@]}"
3 changes: 2 additions & 1 deletion CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ Pre-release builds, then publishes and calls `release-qualification.yml` in
parallel within the same run, using native job dependencies.
Its platform package artifacts are uploaded after versioned images and the chart,
so the installer can use them while qualification runs or after it fails.
The pre-release snap artifact is published to the `latest/beta` channel.
The implemented `release-tag-v1` profile is an incremental subset of RFC 0014
qualification. Failed checks fail the parent workflow and retain evidence in Actions.

Expand Down Expand Up @@ -644,7 +645,7 @@ These workflows run after merge to publish dev/tagged artifacts and verify them.
| `.github/workflows/release-notes.yml` | Shared prompt-driven release notes generation through Inference Hub, used by prerelease previews, notes-only previews, and stable publication when no preview is reused. |
| `.github/workflows/release-qualification.yml` | Reusable qualification suites and result aggregation. Runs security scans, conformance and feature integration, Docker and VM E2E, protobuf compatibility, and advisory compatibility review. |
| `.github/workflows/snap-package.yml` | Builds snap and component artifacts for Release Dev, Release, and Pre-release without Store credentials or publication. |
| `.github/workflows/snap-publish.yml` | Uploads existing snap and component artifacts to the Store without rebuilding. Release Dev calls it directly for `latest/edge` after snap builds and requires it to succeed before creating the dev release. Release calls it through `release-publish.yml` for `latest/stable` after qualification and release publication succeed. |
| `.github/workflows/snap-publish.yml` | Uploads existing snap and component artifacts directly to a Store channel without rebuilding. Release Dev uses `latest/edge`, Pre-release uses `latest/beta`, and Release uses `latest/stable` for the stable rebuild. Uploads queue per channel and architecture, so tagged beta and stable jobs recheck repository tags after earlier publication finishes and before updating their floating channels. |
| `.github/workflows/release-canary.yml` | Smoke-tests published dev artifacts in the `macos`, `ubuntu-deb`, `ubuntu-snap-system-docker`, `fedora`, and `kubernetes` (kind + Helm) jobs. Each job reaches its gateway and creates, exercises, and deletes a sandbox. The `ubuntu-snap-system-docker` job verifies a compatible system Docker lifecycle, while `ubuntu-snap-docker-preflight` tests fail-fast behavior when Docker is absent or supplied by the Docker snap. The positive snap lane also runs a local policy containment check with the packaged prover. It runs automatically after `Release Dev` succeeds and supports manual dispatch (`gh workflow run release-canary.yml --ref <branch>`). See the `test-release-canary` skill for the playbook and local kind reproduction. |

## Required status contexts
Expand Down
12 changes: 11 additions & 1 deletion docs/about/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh |
OPENSHELL_VERSION=pre sh
```

The installer checks prerelease tags from newest to oldest, selects an unexpired published package for the current platform, and downloads only that artifact. The selected candidate may still be qualifying or may have failed qualification. Installed packages keep the candidate's exact version, such as `0.1.0-pre.3`. Prerelease tags do not create entries on the GitHub Releases page. On Linux, prereleases and explicit release tags use Debian or RPM packages.
The installer checks prerelease tags from newest to oldest, selects an unexpired published package for the current platform, and downloads only that artifact. The selected candidate may still be qualifying or may have failed qualification. Installed packages keep the candidate's exact version, such as `0.1.0-pre.3`. Prerelease tags do not create entries on the GitHub Releases page. On Linux, explicit release tags use Debian or RPM packages, as do prereleases by default. Set `OPENSHELL_INSTALL_METHOD=snap` with `OPENSHELL_VERSION=pre` to track the newest published prerelease snap in `latest/beta`. Beta publication runs alongside qualification, so the channel may contain a candidate that later fails qualification.

The rolling [`dev` release](https://github.com/NVIDIA/OpenShell/releases/tag/dev) does not require GitHub authentication:

Expand Down Expand Up @@ -128,6 +128,16 @@ sudo snap install openshell
systemctl --user enable --now snap.openshell.user-gateway.service
```

To install a prerelease build, select the corresponding Store channel:

```shell
sudo snap install openshell --channel=latest/beta
# or, if already installed
sudo snap refresh openshell --channel=latest/beta
```

`latest/beta` contains the newest prerelease published by CI and may not have passed qualification. Stable releases rebuild the selected qualified commit with the final stable version before publishing to `latest/stable`.

The user gateway is disabled globally by default. The install script enables it only for the user who ran the script. After a direct snap installation, choose one user account to run the gateway and enable it with the command above. Do not enable the gateway for multiple users who may be logged in at the same time: every user gateway binds to `127.0.0.1:17670`, so only one can run on the system at a time.

Older OpenShell snap revisions could enable the user gateway globally, causing it to start for every user. Remove that global enablement once, then enable the gateway from the account that will run it:
Expand Down
5 changes: 3 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ NOTES:
x86_64/aarch64, depending on the host package manager. Set
OPENSHELL_INSTALL_METHOD=snap to install the OpenShell snap instead; hosts
that already have the OpenShell snap keep refreshing it. snap installs use
latest/stable by default and latest/edge for dev, and do not support
explicit release tags or prereleases. The OpenShell snap requires a running
latest/stable by default, latest/beta for pre, and latest/edge for dev. They
do not support explicit release tags. The OpenShell snap requires a running
Docker Engine installed from a system package or Docker's package
repository. The Docker snap is not currently compatible with OpenShell.
macOS installs the release Homebrew formula on Apple Silicon and starts a
Expand Down Expand Up @@ -1271,6 +1271,7 @@ install_linux_rpm() {
openshell_snap_channel() {
case "${OPENSHELL_VERSION:-}" in
dev) printf '%s\n' "latest/edge" ;;
pre) printf '%s\n' "latest/beta" ;;
'') printf '%s\n' "latest/stable" ;;
*) error "snap installs do not support OPENSHELL_VERSION=${OPENSHELL_VERSION}; use a native package" ;;
esac
Expand Down
27 changes: 27 additions & 0 deletions tasks/scripts/test-install-sh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ if [ -s "$err" ]; then
exit 1
fi

if ! OPENSHELL_VERSION=pre openshell_snap_channel >"$out" 2>"$err"; then
echo "FAIL: pre must select the latest/beta snap channel" >&2
cat "$err" >&2 || true
exit 1
fi
if [ "$(cat "$out")" != "latest/beta" ]; then
echo "FAIL: pre must select the latest/beta snap channel" >&2
exit 1
fi
if [ -s "$err" ]; then
echo "FAIL: pre must not warn about an ignored version" >&2
cat "$err" >&2 || true
exit 1
fi

if ! OPENSHELL_VERSION="" openshell_snap_channel >"$out" 2>"$err"; then
echo "FAIL: unset OPENSHELL_VERSION must select the latest/stable snap channel" >&2
cat "$err" >&2 || true
Expand Down Expand Up @@ -392,6 +407,18 @@ wait:user-gateway-listener
register:user-gateway
wait:gateway-status"

assert_snap_install_flow \
"prerelease snap tracks beta" \
1 0 "pre" \
"wait:user-docker
root:snap install openshell --channel=latest/beta
root:systemctl --global disable snap.openshell.user-gateway.service
target:systemctl --user daemon-reload
target:systemctl --user enable --now snap.openshell.user-gateway.service
wait:user-gateway-listener
register:user-gateway
wait:gateway-status"

assert_snap_user_refresh_flow

assert_snap_install_flow \
Expand Down
15 changes: 15 additions & 0 deletions tasks/scripts/test-packaging-assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ assert_not_contains "$development_gateway" 'supervisor_image = "{{ openshell_sup
snap_wrapper="${ROOT}/tasks/scripts/snap-gateway-wrapper.sh"
snapcraft="${ROOT}/snapcraft.yaml"
snap_workflow="${ROOT}/.github/workflows/snap-package.yml"
snap_publish_workflow="${ROOT}/.github/workflows/snap-publish.yml"
release_publish_workflow="${ROOT}/.github/workflows/release-publish.yml"
release_prerelease_workflow="${ROOT}/.github/workflows/release-prerelease.yml"
snap_install_docs="${ROOT}/docs/about/installation.mdx"
snap_canary="${ROOT}/.github/workflows/release-canary.yml"
snap_configure_hook="${ROOT}/snap/hooks/configure"
Expand All @@ -126,6 +129,9 @@ package_deb="${ROOT}/tasks/scripts/package-deb.sh"
assert_file_exists "$snap_wrapper"
assert_file_exists "$snapcraft"
assert_file_exists "$snap_workflow"
assert_file_exists "$snap_publish_workflow"
assert_file_exists "$release_publish_workflow"
assert_file_exists "$release_prerelease_workflow"
assert_file_exists "$snap_install_docs"
assert_file_exists "$snap_canary"
assert_file_exists "$snap_configure_hook"
Expand Down Expand Up @@ -208,6 +214,15 @@ bash "$ROOT/tasks/scripts/test-snap-post-refresh-hook.sh" "$snap_post_refresh_ho
assert_contains "$snap_workflow" 'name: openshell-prover-${{ matrix.rust_arch }}-unknown-linux-musl'
assert_contains "$snap_workflow" 'chmod +x prebuilt/prover/openshell-prover'
assert_contains "$snap_workflow" 'cp prebuilt/prover/openshell-prover snap/prebuilt/openshell-prover'
assert_contains "$release_publish_workflow" 'uses: ./.github/workflows/snap-publish.yml'
assert_contains "$release_publish_workflow" 'upload-channel: latest/beta'
assert_contains "$release_publish_workflow" 'github-environment: latest/beta'
assert_contains "$release_prerelease_workflow" 'SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}'
assert_contains "$snap_publish_workflow" 'snapcraft upload --release "${INPUTS_UPLOAD_CHANNEL}"'
assert_contains "$snap_publish_workflow" 'component_args+=(--component "${component_name}=${comp}")'
assert_contains "$snap_publish_workflow" 'group: snap-publish-${{ inputs.upload-channel }}-${{ matrix.arch }}'
assert_contains "$snap_publish_workflow" 'cancel-in-progress: false'
assert_contains "$snap_publish_workflow" 'queue: max'
assert_contains "$snapcraft" 'for bin in openshell openshell-prover openshell-gateway openshell-sandbox openshell-gateway-wrapper; do'
assert_contains "$snapcraft" '"$CRAFT_PART_INSTALL/bin/openshell-prover"'
if ! awk '
Expand Down
Loading