diff --git a/.github/workflows/cli-release-tag.yml b/.github/workflows/cli-release-tag.yml new file mode 100644 index 0000000..744a99c --- /dev/null +++ b/.github/workflows/cli-release-tag.yml @@ -0,0 +1,73 @@ +name: Tag CLI Release + +on: + push: + branches: [main] + paths: + - 'cli/**' + +permissions: + contents: write + +jobs: + bump: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip release]')" + outputs: + tag: ${{ steps.bump.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Bump patch version in cli/pyproject.toml + id: bump + run: | + current=$(grep -m1 '^version = ' cli/pyproject.toml | sed -E 's/version = "(.*)"/\1/') + major=$(echo "$current" | cut -d. -f1) + minor=$(echo "$current" | cut -d. -f2) + patch=$(echo "$current" | cut -d. -f3) + next="$major.$minor.$((patch + 1))" + sed -i "s/^version = \".*\"/version = \"$next\"/" cli/pyproject.toml + echo "tag=cli-v$next" >> "$GITHUB_OUTPUT" + + - name: Commit version bump and push tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add cli/pyproject.toml + git commit -m "cli: bump version to ${{ steps.bump.outputs.tag }} [skip release]" + git push origin HEAD:main + git tag "${{ steps.bump.outputs.tag }}" + git push origin "${{ steps.bump.outputs.tag }}" + + release: + needs: bump + permissions: + contents: write + uses: ./.github/workflows/cli-release.yml + with: + tag: ${{ needs.bump.outputs.tag }} + + publish-pypi: + needs: bump + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.bump.outputs.tag }} + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Build sdist and wheel + working-directory: cli + run: uv build + + - name: Publish to PyPI + working-directory: cli + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + run: uv publish + +# Calls cli-release.yml directly (a GITHUB_TOKEN tag push won't retrigger it); "[skip release]" is a defensive backstop against the same push looping. diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml new file mode 100644 index 0000000..5a5ff60 --- /dev/null +++ b/.github/workflows/cli-release.yml @@ -0,0 +1,83 @@ +name: Release CLI + +on: + push: + tags: + - 'cli-v*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g. cli-v0.1.5)' + required: false + type: string + workflow_call: + inputs: + tag: + required: true + type: string + +permissions: + contents: write + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + asset: canyonos-macos-arm64 + - os: macos-15-intel + asset: canyonos-macos-x86_64 + - os: ubuntu-latest + asset: canyonos-linux-x86_64 + - os: ubuntu-24.04-arm + asset: canyonos-linux-arm64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Install project and Nuitka + working-directory: cli + run: | + uv venv + uv pip install -e . + uv pip install nuitka + + - name: Build standalone binary + working-directory: cli + run: | + uv run python -m nuitka \ + --mode=onefile \ + --output-filename=${{ matrix.asset }} \ + --output-dir=dist \ + --include-data-file=canyonos/dashboard.compose.yml=canyonos/dashboard.compose.yml \ + --include-distribution-metadata=canyonos \ + --assume-yes-for-downloads \ + cli.py + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.asset }} + path: cli/dist/${{ matrix.asset }} + + - name: Attach to GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.tag || github.ref_name }} + files: cli/dist/${{ matrix.asset }} + +# Triggered by: pushing a "cli-v*" tag directly, running manually, or cli-release-tag.yml calling +# this workflow after it auto-tags a cli/pyproject.toml version bump on main. +# cli/install.sh downloads the matching asset for the latest "cli-v*" release. diff --git a/Formula/canyonos.rb b/Formula/canyonos.rb new file mode 100644 index 0000000..140754e --- /dev/null +++ b/Formula/canyonos.rb @@ -0,0 +1,33 @@ +class Canyonos < Formula + desc "CLI for CanyonOS" + homepage "https://github.com/CanyonCodeCoreAI/canyoncodecore" + version "0.1.5" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/CanyonCodeCoreAI/canyoncodecore/releases/download/cli-v#{version}/canyonos-macos-arm64" + sha256 "068136e3e69ea502b8651cb2ab889ee53a7efc5d35723dce85f91b04817bef76" + else + url "https://github.com/CanyonCodeCoreAI/canyoncodecore/releases/download/cli-v#{version}/canyonos-macos-x86_64" + sha256 "c3f5e9adefc27f1cbfc2682138fde779b1865b3b66abba8b0d9dbd17d021526f" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/CanyonCodeCoreAI/canyoncodecore/releases/download/cli-v#{version}/canyonos-linux-arm64" + sha256 "378d4a0a022e27396c531980c7a0808cb5a1d624ff0884f4ae5de0586533be4a" + else + url "https://github.com/CanyonCodeCoreAI/canyoncodecore/releases/download/cli-v#{version}/canyonos-linux-x86_64" + sha256 "1b5add3432079d06b2ec055f4a0da8a65961a33a0fd2f57ca79787ff01ca243f" + end + end + + def install + bin.install Dir["canyonos-*"].first => "canyonos" + end + + test do + system "#{bin}/canyonos", "version" + end +end diff --git a/canyonos_core/Dockerfile b/canyonos_core/Dockerfile index 0edcbe1..adc70d4 100644 --- a/canyonos_core/Dockerfile +++ b/canyonos_core/Dockerfile @@ -17,4 +17,5 @@ EXPOSE 8000 ENTRYPOINT ["python", "-m", "canyonos_core.server"] -# to run: docker build -f canyonos_core/Dockerfile -t saakeths/canyonos:latest . +# to run (single-arch, host platform only): docker build -f canyonos_core/Dockerfile -t saakeths/canyonos:latest . +# to build+push both amd64 and arm64: canyonos_core/build-and-push.sh diff --git a/canyonos_core/build-and-push.sh b/canyonos_core/build-and-push.sh new file mode 100755 index 0000000..bb9448b --- /dev/null +++ b/canyonos_core/build-and-push.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Builds and pushes saakeths/canyonos:latest for linux/amd64 and linux/arm64. +# NOTE: Will need to create an enterprise account on a registry and switch from saakeths to canyonos, this is temporary + +set -eu + +IMAGE="saakeths/canyonos:latest" +PLATFORMS="linux/amd64,linux/arm64" +BUILDER_NAME="canyonos-multiarch" + +if ! docker buildx inspect "$BUILDER_NAME" >/dev/null 2>&1; then + docker buildx create --name "$BUILDER_NAME" --driver docker-container --use +else + docker buildx use "$BUILDER_NAME" +fi + +docker buildx build \ + --builder "$BUILDER_NAME" \ + --platform "$PLATFORMS" \ + -f canyonos_core/Dockerfile \ + -t "$IMAGE" \ + --push \ + . diff --git a/cli/install.sh b/cli/install.sh new file mode 100755 index 0000000..53172b6 --- /dev/null +++ b/cli/install.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# Installs the canyonos CLI from a prebuilt binary attached to the latest +# "cli-v*" GitHub release. Usage: curl -fsSL /install.sh | sh + +set -eu + +REPO="CanyonCodeCoreAI/canyoncodecore" +INSTALL_DIR="${CANYONOS_INSTALL_DIR:-$HOME/.local/bin}" + +os="$(uname -s)" +arch="$(uname -m)" + +case "$os" in + Darwin) + case "$arch" in + arm64) asset="canyonos-macos-arm64" ;; + x86_64) asset="canyonos-macos-x86_64" ;; + *) echo "error: unsupported macOS arch: $arch" >&2; exit 1 ;; + esac + ;; + Linux) + case "$arch" in + x86_64) asset="canyonos-linux-x86_64" ;; + aarch64|arm64) asset="canyonos-linux-arm64" ;; + *) echo "error: unsupported Linux arch: $arch" >&2; exit 1 ;; + esac + ;; + *) + echo "error: unsupported OS: $os (try 'pip install canyonos' instead)" >&2 + exit 1 + ;; +esac + +tag="${CANYONOS_VERSION:-}" +if [ -z "$tag" ]; then + tag="$(curl -fsSL "https://api.github.com/repos/$REPO/releases" \ + | grep '"tag_name"' \ + | grep -o 'cli-v[0-9][^"]*' \ + | head -n1)" +fi + +if [ -z "$tag" ]; then + echo "error: could not find a cli-v* release for $REPO" >&2 + exit 1 +fi + +url="https://github.com/$REPO/releases/download/$tag/$asset" + +echo "Installing canyonos $tag ($asset) to $INSTALL_DIR..." +mkdir -p "$INSTALL_DIR" +curl -fsSL "$url" -o "$INSTALL_DIR/canyonos" +chmod +x "$INSTALL_DIR/canyonos" + +echo "Installed: $INSTALL_DIR/canyonos" +case ":$PATH:" in + *":$INSTALL_DIR:"*) ;; + *) echo "Add $INSTALL_DIR to your PATH to use 'canyonos' directly." ;; +esac diff --git a/cli/pyproject.toml b/cli/pyproject.toml index f11a241..608d6c0 100644 --- a/cli/pyproject.toml +++ b/cli/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "canyonos" -version = "0.1.5" +version = "0.1.711" description = "CanyonOS CLI" requires-python = ">=3.10" dependencies = [ diff --git a/uv.lock b/uv.lock index be54781..21dc886 100644 --- a/uv.lock +++ b/uv.lock @@ -55,7 +55,7 @@ wheels = [ [[package]] name = "canyonos" -version = "0.1.5" +version = "0.1.711" source = { editable = "cli" } dependencies = [ { name = "pyfiglet" },