From 1cb424dceae0e8a4aeeddc6cf9220128bb98bffd Mon Sep 17 00:00:00 2001
From: 1BigBear <181850822+1BigBear@users.noreply.github.com>
Date: Sun, 1 Mar 2026 18:25:55 +0300
Subject: [PATCH 01/10] Add GitHub Actions workflow for ARM build
---
.github/workflows/build-openfang-full-arm.yml | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100644 .github/workflows/build-openfang-full-arm.yml
diff --git a/.github/workflows/build-openfang-full-arm.yml b/.github/workflows/build-openfang-full-arm.yml
new file mode 100644
index 000000000..b4923c04c
--- /dev/null
+++ b/.github/workflows/build-openfang-full-arm.yml
@@ -0,0 +1,47 @@
+name: Build OpenFang Full (ARM Ubuntu)
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-24.04-arm
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: System deps (build)
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential pkg-config clang cmake perl git curl \
+ libssl-dev libsqlite3-dev \
+ libgtk-3-dev libayatana-appindicator3-dev libwebkit2gtk-4.1-dev
+
+ - name: Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Build full workspace
+ env:
+ CARGO_BUILD_JOBS: "2"
+ RUSTFLAGS: "-C debuginfo=0"
+ run: |
+ cargo build --release --workspace --locked
+
+ - name: Collect release binaries
+ run: |
+ mkdir -p out/bin
+ find target/release -maxdepth 1 -type f -executable \
+ ! -name "*.d" ! -name "build-script-*" -exec cp {} out/bin/ \;
+ ls -lah out/bin
+
+ - name: Package
+ run: |
+ tar -C out -czf openfang-full-aarch64-ubuntu24.tar.gz bin
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: openfang-full-aarch64-ubuntu24
+ path: openfang-full-aarch64-ubuntu24.tar.gz
From 29683ca9d072706c888b9f5550409987a2fa7493 Mon Sep 17 00:00:00 2001
From: 1BigBear <181850822+1BigBear@users.noreply.github.com>
Date: Sun, 8 Mar 2026 15:14:53 +0300
Subject: [PATCH 02/10] Add files via upload
---
.github/workflows/release-android.yml | 312 ++++++++++++++++++++++++++
1 file changed, 312 insertions(+)
create mode 100644 .github/workflows/release-android.yml
diff --git a/.github/workflows/release-android.yml b/.github/workflows/release-android.yml
new file mode 100644
index 000000000..c13ce0e45
--- /dev/null
+++ b/.github/workflows/release-android.yml
@@ -0,0 +1,312 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write
+ packages: write
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ # ── Tauri Desktop App (Windows + macOS + Linux) ───────────────────────────
+ # Produces: .msi, .exe (Windows) | .dmg, .app (macOS) | .AppImage, .deb (Linux)
+ # Also generates and uploads latest.json (the auto-updater manifest)
+ desktop:
+ name: Desktop / ${{ matrix.platform.name }}
+ strategy:
+ fail-fast: false
+ matrix:
+ platform:
+ - name: Linux x86_64
+ os: ubuntu-22.04
+ args: "--target x86_64-unknown-linux-gnu"
+ rust_target: x86_64-unknown-linux-gnu
+
+ - name: macOS x86_64
+ os: macos-latest
+ args: "--target x86_64-apple-darwin"
+ rust_target: x86_64-apple-darwin
+
+ - name: macOS ARM64
+ os: macos-latest
+ args: "--target aarch64-apple-darwin"
+ rust_target: aarch64-apple-darwin
+
+ - name: Windows x86_64
+ os: windows-latest
+ args: "--target x86_64-pc-windows-msvc"
+ rust_target: x86_64-pc-windows-msvc
+
+ - name: Windows ARM64
+ os: windows-latest
+ args: "--target aarch64-pc-windows-msvc"
+ rust_target: aarch64-pc-windows-msvc
+
+ runs-on: ${{ matrix.platform.os }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install system deps (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libwebkit2gtk-4.1-dev \
+ libgtk-3-dev \
+ libayatana-appindicator3-dev \
+ librsvg2-dev \
+ patchelf
+
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.platform.rust_target }}
+
+ - uses: Swatinem/rust-cache@v2
+ with:
+ key: desktop-${{ matrix.platform.rust_target }}
+
+ - name: Import macOS signing certificate
+ if: runner.os == 'macOS'
+ env:
+ MAC_CERT_BASE64: ${{ secrets.MAC_CERT_BASE64 }}
+ MAC_CERT_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
+ run: |
+ echo "$MAC_CERT_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
+ security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+ security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+ security import $RUNNER_TEMP/certificate.p12 -P "$MAC_CERT_PASSWORD" \
+ -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
+ security list-keychain -d user -s "$KEYCHAIN_PATH"
+ security set-key-partition-list -S apple-tool:,apple:,codesign: \
+ -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
+ IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}')
+ echo "Using signing identity: $IDENTITY"
+ echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV
+ rm -f $RUNNER_TEMP/certificate.p12
+
+ - name: Build and bundle Tauri desktop app
+ uses: tauri-apps/tauri-action@v0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
+ APPLE_ID: ${{ secrets.MAC_NOTARIZE_APPLE_ID }}
+ APPLE_PASSWORD: ${{ secrets.MAC_NOTARIZE_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.MAC_NOTARIZE_TEAM_ID }}
+ with:
+ tagName: ${{ github.ref_name }}
+ releaseName: "OpenFang ${{ github.ref_name }}"
+ releaseBody: |
+ ## What's New
+
+ See the [CHANGELOG](https://github.com/RightNow-AI/openfang/blob/main/CHANGELOG.md) for full details.
+
+ ## Installation
+
+ **Desktop App** — Download the installer for your platform below.
+
+ **CLI (Linux/macOS)**:
+ ```bash
+ curl -sSf https://openfang.sh | sh
+ ```
+
+ **Docker**:
+ ```bash
+ docker pull ghcr.io/rightnow-ai/openfang:latest
+ ```
+
+ **Coming from OpenClaw?**
+ ```bash
+ openfang migrate --from openclaw
+ ```
+ releaseDraft: false
+ prerelease: false
+ includeUpdaterJson: true
+ projectPath: crates/openfang-desktop
+ args: ${{ matrix.platform.args }}
+
+ # ── CLI Binary (7 platforms + native ARM64 for Android/proot) ────────────
+ cli:
+ name: CLI / ${{ matrix.target }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: x86_64-unknown-linux-gnu
+ os: ubuntu-22.04
+ archive: tar.gz
+ native: false
+
+ - target: aarch64-unknown-linux-gnu
+ os: ubuntu-22.04
+ archive: tar.gz
+ native: false
+
+ # Native ARM64 build on Ubuntu 24 — links against OpenSSL 3.x
+ # Works in proot-distro Ubuntu 24 and nix-on-droid without OpenSSL errors
+ - target: aarch64-unknown-linux-gnu
+ os: ubuntu-24.04-arm
+ archive: tar.gz
+ native: true
+ artifact_name: aarch64-unknown-linux-gnu-native
+
+ # Static musl build — works on Android nix-on-droid without any libc
+ - target: aarch64-unknown-linux-musl
+ os: ubuntu-22.04
+ archive: tar.gz
+ native: false
+
+ - target: x86_64-apple-darwin
+ os: macos-latest
+ archive: tar.gz
+ native: false
+
+ - target: aarch64-apple-darwin
+ os: macos-latest
+ archive: tar.gz
+ native: false
+
+ - target: x86_64-pc-windows-msvc
+ os: windows-latest
+ archive: zip
+ native: false
+
+ - target: aarch64-pc-windows-msvc
+ os: windows-latest
+ archive: zip
+ native: false
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.target }}
+
+ - name: Install build deps (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential pkg-config clang cmake perl curl \
+ libssl-dev libsqlite3-dev
+
+ - name: Install musl cross-compiler (aarch64-musl)
+ if: matrix.target == 'aarch64-unknown-linux-musl'
+ run: sudo apt-get install -y musl-tools gcc-aarch64-linux-gnu
+
+ - name: Install cross (non-native aarch64)
+ if: contains(fromJSON('["aarch64-unknown-linux-gnu","aarch64-unknown-linux-musl"]'), matrix.target) && matrix.native == false
+ run: cargo install cross --locked
+
+ - uses: Swatinem/rust-cache@v2
+ with:
+ key: cli-${{ matrix.target }}-${{ matrix.os }}
+
+ # Native ARM64 build — same approach as build-openfang-full-arm workflow
+ - name: Build CLI (native ARM64)
+ if: matrix.native == true
+ env:
+ CARGO_BUILD_JOBS: "2"
+ RUSTFLAGS: "-C debuginfo=0"
+ run: cargo build --release --locked --bin openfang
+
+ # Cross-compiled aarch64 builds
+ - name: Build CLI (cross)
+ if: contains(fromJSON('["aarch64-unknown-linux-gnu","aarch64-unknown-linux-musl"]'), matrix.target) && matrix.native == false
+ env:
+ OPENSSL_STATIC: "1"
+ OPENSSL_VENDORED: "1"
+ run: cross build --release --target ${{ matrix.target }} --bin openfang
+
+ # All other platforms
+ - name: Build CLI
+ if: matrix.native == false && matrix.target != 'aarch64-unknown-linux-gnu' && matrix.target != 'aarch64-unknown-linux-musl'
+ run: cargo build --release --target ${{ matrix.target }} --bin openfang
+
+ - name: Ad-hoc codesign CLI binary (macOS)
+ if: runner.os == 'macOS'
+ run: codesign --force --sign - target/${{ matrix.target }}/release/openfang
+
+ - name: Set artifact name
+ id: artifact
+ run: |
+ if [ -n "${{ matrix.artifact_name }}" ]; then
+ echo "name=${{ matrix.artifact_name }}" >> $GITHUB_OUTPUT
+ else
+ echo "name=${{ matrix.target }}" >> $GITHUB_OUTPUT
+ fi
+ shell: bash
+
+ - name: Set binary path
+ id: binpath
+ run: |
+ if [ "${{ matrix.native }}" = "true" ]; then
+ echo "path=target/release/openfang" >> $GITHUB_OUTPUT
+ else
+ echo "path=target/${{ matrix.target }}/release/openfang" >> $GITHUB_OUTPUT
+ fi
+ shell: bash
+
+ - name: Package (Unix)
+ if: matrix.archive == 'tar.gz'
+ run: |
+ cp ${{ steps.binpath.outputs.path }} openfang
+ tar czf openfang-${{ steps.artifact.outputs.name }}.tar.gz openfang
+ sha256sum openfang-${{ steps.artifact.outputs.name }}.tar.gz > openfang-${{ steps.artifact.outputs.name }}.tar.gz.sha256
+
+ - name: Package (Windows)
+ if: matrix.archive == 'zip'
+ shell: pwsh
+ run: |
+ Compress-Archive -Path "target/${{ matrix.target }}/release/openfang.exe" -DestinationPath "openfang-${{ matrix.target }}.zip"
+ $hash = (Get-FileHash "openfang-${{ matrix.target }}.zip" -Algorithm SHA256).Hash.ToLower()
+ "$hash openfang-${{ matrix.target }}.zip" | Out-File -Encoding ASCII "openfang-${{ matrix.target }}.zip.sha256"
+
+ - name: Upload to GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: openfang-${{ steps.artifact.outputs.name }}.*
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # ── Docker (linux/amd64 + linux/arm64) ────────────────────────────────────
+ docker:
+ name: Docker Image
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Log in to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Set up QEMU (for arm64 emulation)
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Extract version
+ id: version
+ run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
+ - name: Build and push (multi-arch)
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ tags: |
+ ghcr.io/rightnow-ai/openfang:latest
+ ghcr.io/rightnow-ai/openfang:${{ steps.version.outputs.version }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
From f119a668e1f23db9878c62764aa8b03ba4972a94 Mon Sep 17 00:00:00 2001
From: 1BigBear <181850822+1BigBear@users.noreply.github.com>
Date: Sun, 8 Mar 2026 15:16:24 +0300
Subject: [PATCH 03/10] Add files via upload
---
install.sh | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 177 insertions(+)
create mode 100644 install.sh
diff --git a/install.sh b/install.sh
new file mode 100644
index 000000000..8618a03ee
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+set -eu
+
+# OpenFang Installer (1BigBear fork - with musl/Android/proot support)
+# https://raw.githubusercontent.com/1BigBear/openfang/main/install.sh
+
+REPO="1BigBear/openfang"
+INSTALL_DIR="$HOME/.openfang"
+BIN_DIR="$HOME/.openfang/bin"
+BINARY="openfang"
+
+main() {
+ need_cmd curl
+ need_cmd tar
+ need_cmd uname
+
+ _os="$(uname -s)"
+ _arch="$(uname -m)"
+
+ # Detect environment on Linux aarch64
+ _libc="gnu"
+ _native="false"
+ if [ "$_os" = "Linux" ]; then
+ # Check for musl (nix-on-droid)
+ if ldd /bin/sh 2>&1 | grep -q musl; then
+ _libc="musl"
+ # Check for proot-distro Ubuntu 24 — use native build with OpenSSL 3.x
+ elif [ -f /etc/os-release ]; then
+ _ubuntu_ver="$(grep '^VERSION_ID' /etc/os-release 2>/dev/null | cut -d'"' -f2)"
+ if [ "$_ubuntu_ver" = "24.04" ] || [ "$_ubuntu_ver" = "24.10" ]; then
+ _native="true"
+ fi
+ fi
+ fi
+
+ case "$_os" in
+ Linux)
+ case "$_arch" in
+ x86_64|amd64)
+ _target="x86_64-unknown-linux-gnu"
+ ;;
+ aarch64|arm64)
+ if [ "$_libc" = "musl" ]; then
+ # nix-on-droid — static musl binary, no libc dependency
+ _target="aarch64-unknown-linux-musl"
+ elif [ "$_native" = "true" ]; then
+ # proot-distro Ubuntu 24 — native build with OpenSSL 3.x
+ _target="aarch64-unknown-linux-gnu-native"
+ else
+ # fallback gnu build
+ _target="aarch64-unknown-linux-gnu"
+ fi
+ ;;
+ *)
+ err "Unsupported architecture: $_arch"
+ ;;
+ esac
+ ;;
+ Darwin)
+ case "$_arch" in
+ x86_64|amd64) _target="x86_64-apple-darwin" ;;
+ aarch64|arm64) _target="aarch64-apple-darwin" ;;
+ *) err "Unsupported architecture: $_arch" ;;
+ esac
+ ;;
+ *)
+ err "Unsupported OS: $_os (use irm https://openfang.sh/install.ps1 | iex on Windows)"
+ ;;
+ esac
+
+ _url="https://github.com/${REPO}/releases/latest/download/openfang-${_target}.tar.gz"
+
+ say "Detected: $_os $_arch -> $_target"
+ say "Downloading from: $_url"
+
+ _tmpdir="$(mktemp -d 2>/dev/null || mktemp -d -t openfang)"
+ trap 'rm -rf "$_tmpdir"' EXIT
+
+ _code=$(curl -fsSL -w "%{http_code}" "$_url" -o "${_tmpdir}/openfang.tar.gz") || true
+ if [ "$_code" = "404" ]; then
+ err "Release not found for ${_target}. Check https://github.com/${REPO}/releases"
+ fi
+ if [ ! -f "${_tmpdir}/openfang.tar.gz" ] || [ "$(wc -c < "${_tmpdir}/openfang.tar.gz")" -lt 1000 ]; then
+ err "Download failed (HTTP ${_code}). Check https://github.com/${REPO}/releases"
+ fi
+
+ say "Extracting..."
+ tar -xzf "${_tmpdir}/openfang.tar.gz" -C "$_tmpdir"
+
+ # Find the binary
+ _bin="$(find "$_tmpdir" -name "$BINARY" -type f -perm +111 2>/dev/null | head -1)"
+ if [ -z "$_bin" ]; then
+ _bin="$(find "$_tmpdir" -name "$BINARY" -type f | head -1)"
+ fi
+ if [ -z "$_bin" ]; then
+ err "Could not find openfang binary in archive"
+ fi
+
+ mkdir -p "$BIN_DIR"
+ cp "$_bin" "${BIN_DIR}/${BINARY}"
+ chmod +x "${BIN_DIR}/${BINARY}"
+
+ # Verify it runs
+ if "${BIN_DIR}/${BINARY}" --version >/dev/null 2>&1; then
+ _ver=$("${BIN_DIR}/${BINARY}" --version 2>/dev/null || echo "unknown")
+ say "Installed: $_ver"
+ else
+ say "Installed to: ${BIN_DIR}/${BINARY}"
+ fi
+
+ add_to_path
+
+ say ""
+ say "OpenFang installed successfully!"
+ say ""
+ say " Run: openfang init"
+ say " Docs: https://openfang.sh/docs"
+ say ""
+
+ # Check if binary is reachable
+ if ! command -v openfang >/dev/null 2>&1; then
+ say "Note: restart your shell or run:"
+ say " export PATH=\"${BIN_DIR}:\$PATH\""
+ say ""
+ fi
+}
+
+add_to_path() {
+ # Skip if already in PATH
+ case ":$PATH:" in
+ *":${BIN_DIR}:"*) return ;;
+ esac
+
+ _line="export PATH=\"${BIN_DIR}:\$PATH\""
+
+ # Detect shell profile
+ _profile=""
+ if [ -n "${SHELL:-}" ]; then
+ case "$SHELL" in
+ */zsh) _profile="$HOME/.zshrc" ;;
+ */bash)
+ if [ -f "$HOME/.bashrc" ]; then
+ _profile="$HOME/.bashrc"
+ else
+ _profile="$HOME/.bash_profile"
+ fi ;;
+ */fish) _profile="$HOME/.config/fish/config.fish" ;;
+ *) _profile="$HOME/.profile" ;;
+ esac
+ else
+ _profile="$HOME/.profile"
+ fi
+
+ if [ -n "$_profile" ] && [ -f "$_profile" ]; then
+ if ! grep -q "/.openfang/bin" "$_profile" 2>/dev/null; then
+ printf "\n# OpenFang\n%s\n" "$_line" >> "$_profile"
+ say "Added to PATH via $_profile"
+ fi
+ fi
+}
+
+say() {
+ printf " \033[1;36mopenfang\033[0m %s\n" "$1"
+}
+
+err() {
+ printf " \033[1;36mopenfang\033[0m \033[1;31merror:\033[0m %s\n" "$1" >&2
+ exit 1
+}
+
+need_cmd() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ err "need '$1' (command not found)"
+ fi
+}
+
+main
From dd16ea15bd8101a96d51abb4ae7d3264e64c54ff Mon Sep 17 00:00:00 2001
From: 1BigBear <181850822+1BigBear@users.noreply.github.com>
Date: Sun, 8 Mar 2026 15:20:04 +0300
Subject: [PATCH 04/10] Add workflow_dispatch trigger to release-android.yml
---
.github/workflows/release-android.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/release-android.yml b/.github/workflows/release-android.yml
index c13ce0e45..7de697d12 100644
--- a/.github/workflows/release-android.yml
+++ b/.github/workflows/release-android.yml
@@ -4,6 +4,7 @@ on:
push:
tags:
- "v*"
+ workflow_dispatch:
permissions:
contents: write
From 9c76893fdbb346494198b69698228f80514b0e35 Mon Sep 17 00:00:00 2001
From: 1BigBear <181850822+1BigBear@users.noreply.github.com>
Date: Sun, 8 Mar 2026 16:26:44 +0300
Subject: [PATCH 05/10] Change project title to 'Self experimenting on
OpenFang'
Updated project title to reflect self-experimentation.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 746b84277..cbe0819b0 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
From 4cfed23a28ec7c28fdf161ce02c459b8d6ee8cc7 Mon Sep 17 00:00:00 2001 From: 1BigBear <181850822+1BigBear@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:43:31 +0300 Subject: [PATCH 06/10] Delete .github/workflows/release-android.yml --- .github/workflows/release-android.yml | 313 -------------------------- 1 file changed, 313 deletions(-) delete mode 100644 .github/workflows/release-android.yml diff --git a/.github/workflows/release-android.yml b/.github/workflows/release-android.yml deleted file mode 100644 index 7de697d12..000000000 --- a/.github/workflows/release-android.yml +++ /dev/null @@ -1,313 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*" - workflow_dispatch: - -permissions: - contents: write - packages: write - -env: - CARGO_TERM_COLOR: always - -jobs: - # ── Tauri Desktop App (Windows + macOS + Linux) ─────────────────────────── - # Produces: .msi, .exe (Windows) | .dmg, .app (macOS) | .AppImage, .deb (Linux) - # Also generates and uploads latest.json (the auto-updater manifest) - desktop: - name: Desktop / ${{ matrix.platform.name }} - strategy: - fail-fast: false - matrix: - platform: - - name: Linux x86_64 - os: ubuntu-22.04 - args: "--target x86_64-unknown-linux-gnu" - rust_target: x86_64-unknown-linux-gnu - - - name: macOS x86_64 - os: macos-latest - args: "--target x86_64-apple-darwin" - rust_target: x86_64-apple-darwin - - - name: macOS ARM64 - os: macos-latest - args: "--target aarch64-apple-darwin" - rust_target: aarch64-apple-darwin - - - name: Windows x86_64 - os: windows-latest - args: "--target x86_64-pc-windows-msvc" - rust_target: x86_64-pc-windows-msvc - - - name: Windows ARM64 - os: windows-latest - args: "--target aarch64-pc-windows-msvc" - rust_target: aarch64-pc-windows-msvc - - runs-on: ${{ matrix.platform.os }} - steps: - - uses: actions/checkout@v4 - - - name: Install system deps (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - patchelf - - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.platform.rust_target }} - - - uses: Swatinem/rust-cache@v2 - with: - key: desktop-${{ matrix.platform.rust_target }} - - - name: Import macOS signing certificate - if: runner.os == 'macOS' - env: - MAC_CERT_BASE64: ${{ secrets.MAC_CERT_BASE64 }} - MAC_CERT_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }} - run: | - echo "$MAC_CERT_BASE64" | base64 --decode > $RUNNER_TEMP/certificate.p12 - KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db - KEYCHAIN_PASSWORD=$(openssl rand -base64 32) - security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - security import $RUNNER_TEMP/certificate.p12 -P "$MAC_CERT_PASSWORD" \ - -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" - security list-keychain -d user -s "$KEYCHAIN_PATH" - security set-key-partition-list -S apple-tool:,apple:,codesign: \ - -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" - IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}') - echo "Using signing identity: $IDENTITY" - echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV - rm -f $RUNNER_TEMP/certificate.p12 - - - name: Build and bundle Tauri desktop app - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} - APPLE_ID: ${{ secrets.MAC_NOTARIZE_APPLE_ID }} - APPLE_PASSWORD: ${{ secrets.MAC_NOTARIZE_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.MAC_NOTARIZE_TEAM_ID }} - with: - tagName: ${{ github.ref_name }} - releaseName: "OpenFang ${{ github.ref_name }}" - releaseBody: | - ## What's New - - See the [CHANGELOG](https://github.com/RightNow-AI/openfang/blob/main/CHANGELOG.md) for full details. - - ## Installation - - **Desktop App** — Download the installer for your platform below. - - **CLI (Linux/macOS)**: - ```bash - curl -sSf https://openfang.sh | sh - ``` - - **Docker**: - ```bash - docker pull ghcr.io/rightnow-ai/openfang:latest - ``` - - **Coming from OpenClaw?** - ```bash - openfang migrate --from openclaw - ``` - releaseDraft: false - prerelease: false - includeUpdaterJson: true - projectPath: crates/openfang-desktop - args: ${{ matrix.platform.args }} - - # ── CLI Binary (7 platforms + native ARM64 for Android/proot) ──────────── - cli: - name: CLI / ${{ matrix.target }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-22.04 - archive: tar.gz - native: false - - - target: aarch64-unknown-linux-gnu - os: ubuntu-22.04 - archive: tar.gz - native: false - - # Native ARM64 build on Ubuntu 24 — links against OpenSSL 3.x - # Works in proot-distro Ubuntu 24 and nix-on-droid without OpenSSL errors - - target: aarch64-unknown-linux-gnu - os: ubuntu-24.04-arm - archive: tar.gz - native: true - artifact_name: aarch64-unknown-linux-gnu-native - - # Static musl build — works on Android nix-on-droid without any libc - - target: aarch64-unknown-linux-musl - os: ubuntu-22.04 - archive: tar.gz - native: false - - - target: x86_64-apple-darwin - os: macos-latest - archive: tar.gz - native: false - - - target: aarch64-apple-darwin - os: macos-latest - archive: tar.gz - native: false - - - target: x86_64-pc-windows-msvc - os: windows-latest - archive: zip - native: false - - - target: aarch64-pc-windows-msvc - os: windows-latest - archive: zip - native: false - - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Install build deps (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential pkg-config clang cmake perl curl \ - libssl-dev libsqlite3-dev - - - name: Install musl cross-compiler (aarch64-musl) - if: matrix.target == 'aarch64-unknown-linux-musl' - run: sudo apt-get install -y musl-tools gcc-aarch64-linux-gnu - - - name: Install cross (non-native aarch64) - if: contains(fromJSON('["aarch64-unknown-linux-gnu","aarch64-unknown-linux-musl"]'), matrix.target) && matrix.native == false - run: cargo install cross --locked - - - uses: Swatinem/rust-cache@v2 - with: - key: cli-${{ matrix.target }}-${{ matrix.os }} - - # Native ARM64 build — same approach as build-openfang-full-arm workflow - - name: Build CLI (native ARM64) - if: matrix.native == true - env: - CARGO_BUILD_JOBS: "2" - RUSTFLAGS: "-C debuginfo=0" - run: cargo build --release --locked --bin openfang - - # Cross-compiled aarch64 builds - - name: Build CLI (cross) - if: contains(fromJSON('["aarch64-unknown-linux-gnu","aarch64-unknown-linux-musl"]'), matrix.target) && matrix.native == false - env: - OPENSSL_STATIC: "1" - OPENSSL_VENDORED: "1" - run: cross build --release --target ${{ matrix.target }} --bin openfang - - # All other platforms - - name: Build CLI - if: matrix.native == false && matrix.target != 'aarch64-unknown-linux-gnu' && matrix.target != 'aarch64-unknown-linux-musl' - run: cargo build --release --target ${{ matrix.target }} --bin openfang - - - name: Ad-hoc codesign CLI binary (macOS) - if: runner.os == 'macOS' - run: codesign --force --sign - target/${{ matrix.target }}/release/openfang - - - name: Set artifact name - id: artifact - run: | - if [ -n "${{ matrix.artifact_name }}" ]; then - echo "name=${{ matrix.artifact_name }}" >> $GITHUB_OUTPUT - else - echo "name=${{ matrix.target }}" >> $GITHUB_OUTPUT - fi - shell: bash - - - name: Set binary path - id: binpath - run: | - if [ "${{ matrix.native }}" = "true" ]; then - echo "path=target/release/openfang" >> $GITHUB_OUTPUT - else - echo "path=target/${{ matrix.target }}/release/openfang" >> $GITHUB_OUTPUT - fi - shell: bash - - - name: Package (Unix) - if: matrix.archive == 'tar.gz' - run: | - cp ${{ steps.binpath.outputs.path }} openfang - tar czf openfang-${{ steps.artifact.outputs.name }}.tar.gz openfang - sha256sum openfang-${{ steps.artifact.outputs.name }}.tar.gz > openfang-${{ steps.artifact.outputs.name }}.tar.gz.sha256 - - - name: Package (Windows) - if: matrix.archive == 'zip' - shell: pwsh - run: | - Compress-Archive -Path "target/${{ matrix.target }}/release/openfang.exe" -DestinationPath "openfang-${{ matrix.target }}.zip" - $hash = (Get-FileHash "openfang-${{ matrix.target }}.zip" -Algorithm SHA256).Hash.ToLower() - "$hash openfang-${{ matrix.target }}.zip" | Out-File -Encoding ASCII "openfang-${{ matrix.target }}.zip.sha256" - - - name: Upload to GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: openfang-${{ steps.artifact.outputs.name }}.* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # ── Docker (linux/amd64 + linux/arm64) ──────────────────────────────────── - docker: - name: Docker Image - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Set up QEMU (for arm64 emulation) - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Extract version - id: version - run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" - - name: Build and push (multi-arch) - uses: docker/build-push-action@v6 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ghcr.io/rightnow-ai/openfang:latest - ghcr.io/rightnow-ai/openfang:${{ steps.version.outputs.version }} - cache-from: type=gha - cache-to: type=gha,mode=max From b0b85ceda45acec0bc12fcdeea5b4d8f0d6c8aaa Mon Sep 17 00:00:00 2001 From: 1BigBear <181850822+1BigBear@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:43:57 +0300 Subject: [PATCH 07/10] Delete .github/workflows/build-openfang-full-arm.yml --- .github/workflows/build-openfang-full-arm.yml | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/build-openfang-full-arm.yml diff --git a/.github/workflows/build-openfang-full-arm.yml b/.github/workflows/build-openfang-full-arm.yml deleted file mode 100644 index b4923c04c..000000000 --- a/.github/workflows/build-openfang-full-arm.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Build OpenFang Full (ARM Ubuntu) - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-24.04-arm - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: System deps (build) - run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential pkg-config clang cmake perl git curl \ - libssl-dev libsqlite3-dev \ - libgtk-3-dev libayatana-appindicator3-dev libwebkit2gtk-4.1-dev - - - name: Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Build full workspace - env: - CARGO_BUILD_JOBS: "2" - RUSTFLAGS: "-C debuginfo=0" - run: | - cargo build --release --workspace --locked - - - name: Collect release binaries - run: | - mkdir -p out/bin - find target/release -maxdepth 1 -type f -executable \ - ! -name "*.d" ! -name "build-script-*" -exec cp {} out/bin/ \; - ls -lah out/bin - - - name: Package - run: | - tar -C out -czf openfang-full-aarch64-ubuntu24.tar.gz bin - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: openfang-full-aarch64-ubuntu24 - path: openfang-full-aarch64-ubuntu24.tar.gz From dd40758d257dbde3f9afd92c4c34b787ace60149 Mon Sep 17 00:00:00 2001 From: 1BigBear <181850822+1BigBear@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:51:06 +0300 Subject: [PATCH 08/10] Add files via upload --- .github/workflows/build-openfang-full-arm.yml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/build-openfang-full-arm.yml diff --git a/.github/workflows/build-openfang-full-arm.yml b/.github/workflows/build-openfang-full-arm.yml new file mode 100644 index 000000000..01ebaff6e --- /dev/null +++ b/.github/workflows/build-openfang-full-arm.yml @@ -0,0 +1,69 @@ +name: Build OpenFang Full (ARM Ubuntu) + +on: + workflow_dispatch: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-24.04-arm + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: System deps (build) + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential pkg-config clang cmake perl git curl \ + libssl-dev libsqlite3-dev \ + libgtk-3-dev libayatana-appindicator3-dev libwebkit2gtk-4.1-dev + + - name: Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + with: + key: arm-ubuntu24 + + - name: Build full workspace + env: + CARGO_BUILD_JOBS: "2" + RUSTFLAGS: "-C debuginfo=0" + run: | + cargo build --release --workspace --locked + + - name: Collect release binaries + run: | + mkdir -p out/bin + find target/release -maxdepth 1 -type f -executable \ + ! -name "*.d" ! -name "build-script-*" -exec cp {} out/bin/ \; + ls -lah out/bin + + - name: Package + run: | + tar -C out -czf openfang-aarch64-unknown-linux-gnu-native.tar.gz bin + sha256sum openfang-aarch64-unknown-linux-gnu-native.tar.gz > openfang-aarch64-unknown-linux-gnu-native.tar.gz.sha256 + + - name: Upload artifact (manual run) + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: openfang-aarch64-unknown-linux-gnu-native + path: openfang-aarch64-unknown-linux-gnu-native.tar.gz + + - name: Upload to GitHub Release (tag run) + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: | + openfang-aarch64-unknown-linux-gnu-native.tar.gz + openfang-aarch64-unknown-linux-gnu-native.tar.gz.sha256 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0a6fd20eea3ca250bcbb89cd8a73af3314f94248 Mon Sep 17 00:00:00 2001 From: 1BigBear <181850822+1BigBear@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:47:46 +0300 Subject: [PATCH 09/10] Delete install.sh --- install.sh | 177 ----------------------------------------------------- 1 file changed, 177 deletions(-) delete mode 100644 install.sh diff --git a/install.sh b/install.sh deleted file mode 100644 index 8618a03ee..000000000 --- a/install.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/sh -set -eu - -# OpenFang Installer (1BigBear fork - with musl/Android/proot support) -# https://raw.githubusercontent.com/1BigBear/openfang/main/install.sh - -REPO="1BigBear/openfang" -INSTALL_DIR="$HOME/.openfang" -BIN_DIR="$HOME/.openfang/bin" -BINARY="openfang" - -main() { - need_cmd curl - need_cmd tar - need_cmd uname - - _os="$(uname -s)" - _arch="$(uname -m)" - - # Detect environment on Linux aarch64 - _libc="gnu" - _native="false" - if [ "$_os" = "Linux" ]; then - # Check for musl (nix-on-droid) - if ldd /bin/sh 2>&1 | grep -q musl; then - _libc="musl" - # Check for proot-distro Ubuntu 24 — use native build with OpenSSL 3.x - elif [ -f /etc/os-release ]; then - _ubuntu_ver="$(grep '^VERSION_ID' /etc/os-release 2>/dev/null | cut -d'"' -f2)" - if [ "$_ubuntu_ver" = "24.04" ] || [ "$_ubuntu_ver" = "24.10" ]; then - _native="true" - fi - fi - fi - - case "$_os" in - Linux) - case "$_arch" in - x86_64|amd64) - _target="x86_64-unknown-linux-gnu" - ;; - aarch64|arm64) - if [ "$_libc" = "musl" ]; then - # nix-on-droid — static musl binary, no libc dependency - _target="aarch64-unknown-linux-musl" - elif [ "$_native" = "true" ]; then - # proot-distro Ubuntu 24 — native build with OpenSSL 3.x - _target="aarch64-unknown-linux-gnu-native" - else - # fallback gnu build - _target="aarch64-unknown-linux-gnu" - fi - ;; - *) - err "Unsupported architecture: $_arch" - ;; - esac - ;; - Darwin) - case "$_arch" in - x86_64|amd64) _target="x86_64-apple-darwin" ;; - aarch64|arm64) _target="aarch64-apple-darwin" ;; - *) err "Unsupported architecture: $_arch" ;; - esac - ;; - *) - err "Unsupported OS: $_os (use irm https://openfang.sh/install.ps1 | iex on Windows)" - ;; - esac - - _url="https://github.com/${REPO}/releases/latest/download/openfang-${_target}.tar.gz" - - say "Detected: $_os $_arch -> $_target" - say "Downloading from: $_url" - - _tmpdir="$(mktemp -d 2>/dev/null || mktemp -d -t openfang)" - trap 'rm -rf "$_tmpdir"' EXIT - - _code=$(curl -fsSL -w "%{http_code}" "$_url" -o "${_tmpdir}/openfang.tar.gz") || true - if [ "$_code" = "404" ]; then - err "Release not found for ${_target}. Check https://github.com/${REPO}/releases" - fi - if [ ! -f "${_tmpdir}/openfang.tar.gz" ] || [ "$(wc -c < "${_tmpdir}/openfang.tar.gz")" -lt 1000 ]; then - err "Download failed (HTTP ${_code}). Check https://github.com/${REPO}/releases" - fi - - say "Extracting..." - tar -xzf "${_tmpdir}/openfang.tar.gz" -C "$_tmpdir" - - # Find the binary - _bin="$(find "$_tmpdir" -name "$BINARY" -type f -perm +111 2>/dev/null | head -1)" - if [ -z "$_bin" ]; then - _bin="$(find "$_tmpdir" -name "$BINARY" -type f | head -1)" - fi - if [ -z "$_bin" ]; then - err "Could not find openfang binary in archive" - fi - - mkdir -p "$BIN_DIR" - cp "$_bin" "${BIN_DIR}/${BINARY}" - chmod +x "${BIN_DIR}/${BINARY}" - - # Verify it runs - if "${BIN_DIR}/${BINARY}" --version >/dev/null 2>&1; then - _ver=$("${BIN_DIR}/${BINARY}" --version 2>/dev/null || echo "unknown") - say "Installed: $_ver" - else - say "Installed to: ${BIN_DIR}/${BINARY}" - fi - - add_to_path - - say "" - say "OpenFang installed successfully!" - say "" - say " Run: openfang init" - say " Docs: https://openfang.sh/docs" - say "" - - # Check if binary is reachable - if ! command -v openfang >/dev/null 2>&1; then - say "Note: restart your shell or run:" - say " export PATH=\"${BIN_DIR}:\$PATH\"" - say "" - fi -} - -add_to_path() { - # Skip if already in PATH - case ":$PATH:" in - *":${BIN_DIR}:"*) return ;; - esac - - _line="export PATH=\"${BIN_DIR}:\$PATH\"" - - # Detect shell profile - _profile="" - if [ -n "${SHELL:-}" ]; then - case "$SHELL" in - */zsh) _profile="$HOME/.zshrc" ;; - */bash) - if [ -f "$HOME/.bashrc" ]; then - _profile="$HOME/.bashrc" - else - _profile="$HOME/.bash_profile" - fi ;; - */fish) _profile="$HOME/.config/fish/config.fish" ;; - *) _profile="$HOME/.profile" ;; - esac - else - _profile="$HOME/.profile" - fi - - if [ -n "$_profile" ] && [ -f "$_profile" ]; then - if ! grep -q "/.openfang/bin" "$_profile" 2>/dev/null; then - printf "\n# OpenFang\n%s\n" "$_line" >> "$_profile" - say "Added to PATH via $_profile" - fi - fi -} - -say() { - printf " \033[1;36mopenfang\033[0m %s\n" "$1" -} - -err() { - printf " \033[1;36mopenfang\033[0m \033[1;31merror:\033[0m %s\n" "$1" >&2 - exit 1 -} - -need_cmd() { - if ! command -v "$1" >/dev/null 2>&1; then - err "need '$1' (command not found)" - fi -} - -main From 3b436ab24efd704d042f7b752f0cefda2b562193 Mon Sep 17 00:00:00 2001 From: 1BigBear <181850822+1BigBear@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:49:54 +0300 Subject: [PATCH 10/10] Add files via upload --- install.sh | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 install.sh diff --git a/install.sh b/install.sh new file mode 100644 index 000000000..925626bc5 --- /dev/null +++ b/install.sh @@ -0,0 +1,147 @@ +#!/bin/sh +set -eu + +# OpenFang Installer (1BigBear fork - proot-distro ARM64 support) +# https://raw.githubusercontent.com/1BigBear/openfang/main/install.sh + +REPO="1BigBear/openfang" +INSTALL_DIR="$HOME/.openfang" +BIN_DIR="$HOME/.openfang/bin" +BINARY="openfang" + +main() { + need_cmd curl + need_cmd tar + need_cmd uname + + _os="$(uname -s)" + _arch="$(uname -m)" + + case "$_os" in + Linux) + case "$_arch" in + x86_64|amd64) + _target="x86_64-unknown-linux-gnu" + ;; + aarch64|arm64) + _target="aarch64-unknown-linux-gnu-native" + ;; + *) + err "Unsupported architecture: $_arch" + ;; + esac + ;; + Darwin) + case "$_arch" in + x86_64|amd64) _target="x86_64-apple-darwin" ;; + aarch64|arm64) _target="aarch64-apple-darwin" ;; + *) err "Unsupported architecture: $_arch" ;; + esac + ;; + *) + err "Unsupported OS: $_os" + ;; + esac + + _url="https://github.com/${REPO}/releases/latest/download/openfang-${_target}.tar.gz" + + say "Detected: $_os $_arch -> $_target" + say "Downloading from: $_url" + + _tmpdir="$(mktemp -d 2>/dev/null || mktemp -d -t openfang)" + trap 'rm -rf "$_tmpdir"' EXIT + + _code=$(curl -fsSL -w "%{http_code}" "$_url" -o "${_tmpdir}/openfang.tar.gz") || true + if [ "$_code" = "404" ]; then + err "Release not found. Check https://github.com/${REPO}/releases" + fi + if [ ! -f "${_tmpdir}/openfang.tar.gz" ] || [ "$(wc -c < "${_tmpdir}/openfang.tar.gz")" -lt 1000 ]; then + err "Download failed (HTTP ${_code}). Check https://github.com/${REPO}/releases" + fi + + say "Extracting..." + tar -xzf "${_tmpdir}/openfang.tar.gz" -C "$_tmpdir" + + _bin="$(find "$_tmpdir" -name "$BINARY" -type f -perm +111 2>/dev/null | head -1)" + if [ -z "$_bin" ]; then + _bin="$(find "$_tmpdir" -name "$BINARY" -type f | head -1)" + fi + if [ -z "$_bin" ]; then + err "Could not find openfang binary in archive" + fi + + mkdir -p "$BIN_DIR" + cp "$_bin" "${BIN_DIR}/${BINARY}" + chmod +x "${BIN_DIR}/${BINARY}" + + if "${BIN_DIR}/${BINARY}" --version >/dev/null 2>&1; then + _ver=$("${BIN_DIR}/${BINARY}" --version 2>/dev/null || echo "unknown") + say "Installed: $_ver" + else + say "Installed to: ${BIN_DIR}/${BINARY}" + fi + + add_to_path + + say "" + say "OpenFang installed successfully!" + say "" + say " Run: openfang init" + say " Docs: https://github.com/${REPO}" + say "" + + if ! command -v openfang >/dev/null 2>&1; then + say "Note: restart your shell or run:" + say " export PATH=\"${BIN_DIR}:\$PATH\"" + say "" + fi +} + +add_to_path() { + case ":$PATH:" in + *":${BIN_DIR}:"*) return ;; + esac + + _line="export PATH=\"${BIN_DIR}:\$PATH\"" + + _profile="" + if [ -n "${SHELL:-}" ]; then + case "$SHELL" in + */zsh) _profile="$HOME/.zshrc" ;; + */bash) + if [ -f "$HOME/.bashrc" ]; then + _profile="$HOME/.bashrc" + else + _profile="$HOME/.bash_profile" + fi ;; + */fish) _profile="$HOME/.config/fish/config.fish" ;; + *) _profile="$HOME/.profile" ;; + esac + else + _profile="$HOME/.profile" + fi + + if [ -n "$_profile" ] && [ -f "$_profile" ]; then + if ! grep -q "/.openfang/bin" "$_profile" 2>/dev/null; then + printf "\n# OpenFang\n%s\n" "$_line" >> "$_profile" + say "Added to PATH via $_profile" + fi + fi +} + +say() { + printf " \033[1;36mopenfang\033[0m %s\n" "$1" +} + +err() { + printf " \033[1;36mopenfang\033[0m \033[1;31merror:\033[0m %s\n" "$1" >&2 + exit 1 +} + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + err "need '$1' (command not found)" + fi +} + +main