Skip to content
Closed
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
112 changes: 86 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@ name: package
# Build .deb + .rpm for MiniMax Code from the upstream macOS DMG and publish
# them to a GitHub Release tagged minimax-code-v<version>.
#
# MiniMax ships TWO separate editions of the desktop app:
# cn agent.minimax.cn (com.minimax.agent.cn, Chinese UI)
# overseas agent.minimax.io (com.minimax.agent, English UI)
# They are different payloads (app.asar ships a different .env.local), so each
# edition is built from its own upstream DMG and published as its own package
# and release tag:
# cn minimax-code-v<version> minimax-code_*.deb
# overseas minimax-code-global-v<version> minimax-code-global_*.deb
# The two packages have distinct names, so they can be installed side by side.
#
# Triggers:
# - workflow_dispatch: build one explicit version. Pass `dmg_url` to point at a
# fetchable DMG; if omitted, packaging/resolve-version.sh resolves it (works
# once MiniMax publishes a latest-mac.yml at their electron-updater URL).
# - schedule (every 6h): discover the latest upstream version; if it is not yet
# released here, dispatch a build. (workflow_dispatch is the one event the
# GITHUB_TOKEN may trigger recursively.)
# - workflow_dispatch: build one explicit version. Pass a DMG URL to point at
# a fetchable DMG; if omitted, packaging/resolve-version.sh resolves it
# (works once MiniMax publishes a latest-mac.yml at their electron-updater
# URL). Use the region input to build a single edition.
# - schedule (every 6h): discover the latest upstream version; if any
# edition of it is not yet released, dispatch a build for it. (workflow_
# dispatch is the one event the GITHUB_TOKEN may trigger recursively.)
#
# Note: as of writing, MiniMax has not published latest-mac.yml at
# https://filecdn.minimax.chat/public/minimax-agent/release, so scheduled
# discovery no-ops until they do; use workflow_dispatch + dmg_url in the meantime.
# https://filecdn.minimax.chat/public/minimax-agent-prod/release, so scheduled
# discovery no-ops until they do; use workflow_dispatch + a DMG URL in the
# meantime.

on:
workflow_dispatch:
Expand All @@ -22,8 +34,21 @@ on:
description: "Upstream version, e.g. 3.0.43"
required: true
type: string
dmg_url:
description: "Direct DMG URL (optional; auto-resolved if omitted)"
region:
description: "Edition(s) to build"
required: false
default: both
type: choice
options:
- both
- cn
- overseas
dmg_url_cn:
description: "Direct DMG URL for the CN edition (optional; auto-resolved if omitted)"
required: false
type: string
dmg_url_overseas:
description: "Direct DMG URL for the global edition (optional; auto-resolved if omitted)"
required: false
type: string
schedule:
Expand All @@ -44,22 +69,34 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -uo pipefail
if ! out="$(./packaging/resolve-version.sh 2>/dev/null)"; then
if ! out="$(MMX_REGION=cn ./packaging/resolve-version.sh 2>/dev/null)"; then
echo "No resolvable upstream version yet (latest-mac.yml not published); skipping."
exit 0
fi
ver="$(printf '%s\n' "$out" | sed -n '1p')"
[ -n "${ver:-}" ] || exit 0
echo "Latest MiniMax Code: $ver"
if gh release view "minimax-code-v${ver}" -R "${{ github.repository }}" >/dev/null 2>&1; then
echo "minimax-code-v${ver} already released; nothing to do."
else
gh workflow run build.yml -R "${{ github.repository }}" -f version="${ver}"
fi
for region in cn overseas; do
tag="$(MMX_REGION="$region" bash -c \
'. ./scripts/lib_common.sh >/dev/null 2>&1; printf "%s%s" "$MMX_RELEASE_TAG_PREFIX" "$1"' _ "$ver")"
if gh release view "$tag" -R "${{ github.repository }}" >/dev/null 2>&1; then
echo "$tag already released."
else
echo "Missing release: $tag"
gh workflow run build.yml -R "${{ github.repository }}" \
-f version="${ver}" -f region="${region}"
fi
done

build:
if: github.event_name == 'workflow_dispatch'
if: github.event_name == 'workflow_dispatch' && (inputs.region == 'both' || inputs.region == matrix.region)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
region: [cn, overseas]
env:
MMX_REGION: ${{ matrix.region }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -77,16 +114,21 @@ jobs:
- name: Resolve DMG URL
id: dmg
env:
INPUT_DMG_URL: ${{ inputs.dmg_url }}
DMG_URL_CN: ${{ inputs.dmg_url_cn }}
DMG_URL_OVERSEAS: ${{ inputs.dmg_url_overseas }}
run: |
if [ -n "$INPUT_DMG_URL" ]; then
echo "url=$INPUT_DMG_URL" >> "$GITHUB_OUTPUT"
set -uo pipefail
if [ "$MMX_REGION" = "cn" ]; then
url="${DMG_URL_CN:-}"
else
url="${DMG_URL_OVERSEAS:-}"
fi
if [ -z "$url" ]; then
out="$(./packaging/resolve-version.sh)"
url="$(printf '%s\n' "$out" | sed -n '2p')"
[ -n "$url" ] || { echo "Could not resolve DMG URL" >&2; exit 1; }
echo "url=$url" >> "$GITHUB_OUTPUT"
fi
echo "url=$url" >> "$GITHUB_OUTPUT"

- name: Build packages
env:
Expand All @@ -97,39 +139,57 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: packages
name: packages-${{ matrix.region }}
path: dist/
if-no-files-found: error
retention-days: 14

release:
needs: build
# always(): the two editions are built and published independently, so one
# edition failing must not stop the other from being released.
if: always() && needs.build.result != 'cancelled' && needs.build.result != 'skipped' && (inputs.region == 'both' || inputs.region == matrix.region)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
region: [cn, overseas]
env:
VER: ${{ inputs.version }}
MMX_REGION: ${{ matrix.region }}
steps:
- uses: actions/checkout@v4
- name: Download all package artifacts
- name: Download package artifacts
uses: actions/download-artifact@v4
with:
pattern: packages-${{ matrix.region }}
path: dist
merge-multiple: true
- name: Create or update release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
TAG="minimax-code-v${VER}"
. ./scripts/lib_common.sh
TAG="${MMX_RELEASE_TAG_PREFIX}${VER}"
case "$MMX_REGION" in
cn)
EDITION="MiniMax Code (CN edition, agent.minimax.cn)" ;;
overseas)
EDITION="MiniMax Code (global edition, agent.minimax.io)" ;;
esac
{
echo "MiniMax Code ${VER} — unofficial Linux packages"
echo
echo "Edition: ${EDITION}"
echo
echo "Repackaged from MiniMax's official macOS build. MiniMax Code is (c) MiniMax;"
echo "this project only rewraps the upstream binaries for Linux. See NOTICE.md."
echo
echo "## Install"
echo '```bash'
echo "sudo apt install ./minimax-code_${VER}_amd64.deb # Debian/Ubuntu"
echo "sudo dnf install ./minimax-code-${VER}.x86_64.rpm # Fedora/RHEL"
for f in dist/*.deb; do echo "sudo apt install ./$(basename "$f") # Debian/Ubuntu"; done
for f in dist/*.rpm; do echo "sudo dnf install ./$(basename "$f") # Fedora/RHEL"; done
echo '```'
echo
echo "## Assets"
Expand Down
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,31 @@ of native addons for Linux, and wraps the result in installable packages.

## Install

Download the files for your distribution and architecture from
[Releases](../../releases) (tagged `minimax-code-v<version>`), then:
MiniMax ships **two separate editions** of the desktop app. They are different
applications with different backends, accounts and UI languages, and each is
packaged here:

| Edition | Backend | Release tag | Package |
| --- | --- | --- | --- |
| CN | `agent.minimax.cn` (Chinese UI) | `minimax-code-v<version>` | `minimax-code_*` |
| Global | `agent.minimax.io` (English UI) | `minimax-code-global-v<version>` | `minimax-code-global_*` |

Download the files for your distribution from
[Releases](../../releases) — pick the tag that matches your edition — then:

```bash
# Debian / Ubuntu / Mint / Pop!_OS
sudo apt install ./minimax-code_3.0.43_amd64.deb
sudo apt install ./minimax-code_3.0.43_amd64.deb # CN edition
sudo apt install ./minimax-code-global_3.0.43_amd64.deb # global edition

# Fedora / RHEL / openSUSE
sudo dnf install ./minimax-code-3.0.43.x86_64.rpm
sudo dnf install ./minimax-code-3.0.43.x86_64.rpm # CN edition
sudo dnf install ./minimax-code-global-3.0.43.x86_64.rpm # global edition
```

Launch from your application menu, or run `minimax-code` in a terminal. Verify
The two packages have different names and install paths, so both can be
installed side by side. Launch from your application menu, or run
`minimax-code` (CN) / `minimax-code-global` (global) in a terminal. Verify
downloads against the `checksums_*.txt` file in each release.

> The first run may need to install or update the agent's CLI/runtime; follow
Expand All @@ -55,14 +68,18 @@ app's built-in macOS/Windows auto-updater.
## How new versions get packaged

A GitHub Actions workflow (see [`.github/workflows/build.yml`](.github/workflows/build.yml))
runs on a schedule, discovers the latest MiniMax Code version, and — if that
version is not already released — builds packages for both architectures and
publishes them to a GitHub Release tagged `minimax-code-v<version>`.
runs on a schedule, discovers the latest MiniMax Code version, and — for each
edition that is not already released — builds its packages and publishes them
to a GitHub Release tagged `minimax-code-v<version>` (CN) and
`minimax-code-global-v<version>` (global).

The "is it released?" check uses the release tag as the source of truth: each
successful build creates `minimax-code-v<version>`, so the set of tags is the
successful build creates `<package>-v<version>`, so the set of tags is the
record of what has been packaged. A build can also be triggered manually with
`workflow_dispatch` (optionally pasting an explicit DMG URL).
`workflow_dispatch` (choose one edition or both, and optionally paste an
explicit DMG URL per edition).

Packages are built for **x86-64** only.

## Build locally

Expand All @@ -77,9 +94,17 @@ Requirements: `curl`, `7z` (p7zip), `perl`, `python3`, `node`/`npm` (for

# Produce .deb + .rpm from a local DMG (into dist/):
PRODUCT_VERSION=3.0.43 DMG="MiniMax Code-3.0.43.dmg" ./packaging/build.sh

# Package the global edition instead (agent.minimax.io):
MMX_REGION=overseas PRODUCT_VERSION=3.0.43 DMG="MiniMax Code-3.0.43.dmg" \
./packaging/build.sh
```

`ARCH` is `x64`. The DMG is obtained from MiniMax's official download.
`ARCH` is `x64`. `MMX_REGION` is `cn` (default) or `overseas` and selects both
the upstream DMG and the package identity (app id, package name, install
path). The build verifies that the payload it extracted really is the edition
that was requested and refuses to package a mismatch. The DMG is obtained from
MiniMax's official download.

Both entry points are **non-destructive on failure**: arguments and the output
directory are validated up front (dangerous targets such as `/`, system
Expand Down
22 changes: 22 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ asar_extract_app "$CONTENTS" "$STAGING/resources/app"
# covered its contents, so drop the duplicate directory.
rm -rf "$STAGING/resources/app.asar.unpacked"

# The upstream ships a SEPARATE payload per edition (app.asar carries a
# different .env.local with a different backend host). Packaging the wrong one
# produces a package that installs cleanly and then talks to the wrong
# backend, so verify the payload's edition against the one we asked for and
# fail the build on a mismatch.
info "==> Verifying payload edition (MMX_REGION=$MMX_REGION)"
PAYLOAD_ENV="$STAGING/resources/app/.env.local"
PAYLOAD_DOMAIN=""
if [ -f "$PAYLOAD_ENV" ]; then
PAYLOAD_DOMAIN="$(sed -n 's/^NEXT_PUBLIC_DOMAIN_URL="\{0,1\}\([^"]*\)"\{0,1\}$/\1/p' "$PAYLOAD_ENV" | head -1)"
fi
if [ -z "$PAYLOAD_DOMAIN" ]; then
warn "could not read NEXT_PUBLIC_DOMAIN_URL from $PAYLOAD_ENV (upstream layout changed?); edition not verified"
elif [ "$PAYLOAD_DOMAIN" != "$MMX_REGION_DOMAIN" ]; then
die "Edition mismatch: MMX_REGION=$MMX_REGION expects $MMX_REGION_DOMAIN, but the payload reports $PAYLOAD_DOMAIN.
The upstream DMG for this edition was not the one that was downloaded; refusing to package it."
else
info " payload edition OK: $PAYLOAD_DOMAIN"
fi

info "==> Applying Linux adaptation patches"
node "$SCRIPT_DIR/scripts/patch_linux.js" "$STAGING/resources/app" "$MMX_PKG_NAME"

Expand Down Expand Up @@ -196,6 +216,8 @@ cat > "$STAGING/build-info.json" <<EOF
"electron_version": "$ELECTRON_VERSION",
"target_arch": "linux-$MMX_ELECTRON_ARCH",
"app_id": "$MMX_APP_ID",
"region": "$MMX_REGION",
"domain": "${PAYLOAD_DOMAIN:-$MMX_REGION_DOMAIN}",
"unofficial": true
}
EOF
Expand Down
18 changes: 18 additions & 0 deletions packaging/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ fi
ELECTRON_VERSION="$(node -e "console.log(require('$PAYLOAD/build-info.json').electron_version)")"
info "Packaging MiniMax Code $PRODUCT_VERSION (Electron $ELECTRON_VERSION) $MMX_DEB_ARCH/$MMX_RPM_ARCH"

# ---- Stage icons under this package's own name ----------------------------
# The canonical icon tree under packaging/icons/ is named after the CN package.
# Each edition installs its own copy (same artwork, own file name) so two
# editions never claim the same file and can be installed side by side.
ICON_SRC="${MMX_ICON_SRC:-$ROOT/packaging/icons/minimax-code}"
[ -d "$ICON_SRC" ] || die "Icon source directory not found: $ICON_SRC"
rm -rf "$BUILD/icons"
ICON_STAGE="$BUILD/icons/$MMX_PKG_NAME"
mkdir -p "$ICON_STAGE"
cp -a "$ICON_SRC/." "$ICON_STAGE/"
ICON_BASE="$(basename "$ICON_SRC")"
find "$ICON_STAGE" -type f -name "$ICON_BASE.png" | while IFS= read -r icon; do
dest="$(dirname "$icon")/$MMX_PKG_NAME.png"
# The CN edition already carries the package name; mv would refuse it.
[ "$icon" = "$dest" ] || mv -f "$icon" "$dest"
done

# ---- Render templates (perl ${VAR}) -----------------------------------------
render() { perl -pe 's/\$\{(\w+)\}/defined $ENV{$1} ? $ENV{$1} : ""/ge' "$1" > "$2"; }

Expand All @@ -68,6 +85,7 @@ VERSION="$PRODUCT_VERSION" ELECTRON_VERSION="$ELECTRON_VERSION" \
NFPM_ARCH="$MMX_DEB_ARCH" \
render "$ROOT/packaging/templates/nfpm.yaml.tmpl" "$BUILD/nfpm.yaml"
PKG_NAME="$MMX_PKG_NAME" DISPLAY="$MMX_DISPLAY" WMCLASS="$MMX_WMCLASS" INSTALL_PREFIX="$MMX_INSTALL_PREFIX" \
MIME_TYPE="x-scheme-handler/${MMX_SCHEME};" \
render "$ROOT/packaging/templates/desktop.tmpl" "$BUILD/$MMX_PKG_NAME.desktop"
PKG_NAME="$MMX_PKG_NAME" INSTALL_PREFIX="$MMX_INSTALL_PREFIX" \
render "$ROOT/packaging/templates/wrapper.tmpl" "$BUILD/wrapper"
Expand Down
8 changes: 7 additions & 1 deletion packaging/resolve-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@
# URLs with the version embedded in the filename. Anonymously fetchable, no login.
# Strategy 2 (fallback): electron-updater latest-mac.yml at their release URL.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=../scripts/lib_common.sh
. "$ROOT/scripts/lib_common.sh"

CONFIG_URL="${MMX_CONFIG_URL:-https://agent.minimaxi.com/v1/api/config/web/common_config}"

json="$(curl -fsSL --max-time 20 -A "Mozilla/5.0" "$CONFIG_URL" 2>/dev/null || true)"
if [ -n "$json" ]; then
# MMX_REGION selects which edition to package: "cn" (default, com.minimax.agent.cn,
# filecdn.minimax.chat, minimax-cn scheme) or "overseas" (file.cdn.minimax.io).
# The per-edition identity used by the rest of the build lives in
# scripts/lib_common.sh; keep the two in sync.
url="$(printf '%s\n' "$json" | MMX_REGION="${MMX_REGION:-cn}" python3 -c '
import os, sys, json
try:
Expand All @@ -42,7 +48,7 @@ for k in keys:
fi
fi

MANIFEST="${MMX_MANIFEST_URL:-https://filecdn.minimax.chat/public/minimax-agent/release}"
MANIFEST="${MMX_MANIFEST_URL:-$MMX_REGION_MANIFEST}"
if mac="$(curl -fsSL --max-time 20 "$MANIFEST/latest-mac.yml" 2>/dev/null)" && [ -n "$mac" ]; then
ver="$(printf '%s\n' "$mac" | awk '/^version:/{print $2; exit}')"
rel="$(printf '%s\n' "$mac" | awk '/^[[:space:]]+-[[:space:]]+url:/{print $3; exit}')"
Expand Down
2 changes: 1 addition & 1 deletion packaging/templates/desktop.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ StartupNotify=true
StartupWMClass=${WMCLASS}
Categories=Development;IDE;Utility;
Terminal=false
MimeType=x-scheme-handler/minimax-cn;x-scheme-handler/minimax;
MimeType=${MIME_TYPE}

2 changes: 1 addition & 1 deletion packaging/templates/nfpm.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ contents:
dst: /usr/bin/${PKG_NAME}
- src: build/${PKG_NAME}.desktop
dst: /usr/share/applications/${PKG_NAME}.desktop
- src: packaging/icons/${PKG_NAME}/hicolor
- src: build/icons/${PKG_NAME}/hicolor
dst: /usr/share/icons/hicolor

scripts:
Expand Down
Loading