mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
feat(release): build and verify an AppImage of Studio on Linux (download only)
This commit is contained in:
1 parent
edcbc47c06
commit
d136ac2828
11 files changed
+94
-10
No files matched your search
@@ -389,6 +389,14 @@ jobs:
|
||||
version="${{ needs.resolve.outputs.version }}"
|
||||
bash scripts/linux-install-smoke.sh "dist/ReasonixStudio-linux-$arch.deb" "$arch" "${version#v}"
|
||||
|
||||
- name: Verify the AppImage
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
platform="${{ matrix.platform }}"
|
||||
arch="${platform#linux/}"
|
||||
bash scripts/verify-linux-appimage.sh "dist/ReasonixStudio-linux-$arch.AppImage" "$arch"
|
||||
|
||||
# With signing on, this leg only hands the bundle on: the signing jobs
|
||||
# run nothing but the signer, and windows-package packages in between.
|
||||
- name: Upload the unsigned Windows bundle for signing
|
||||
@@ -1199,7 +1207,7 @@ jobs:
|
||||
fi
|
||||
{
|
||||
printf '%s\n\n' '---'
|
||||
printf "**macOS** 已用 Developer ID 签名并公证,打开时不需要清除隔离属性。%s**Linux** Debian 系从 \`.deb\` 安装,Fedora、RHEL、openSUSE 等从 \`.rpm\` 安装(\`sudo dnf install ./ReasonixStudio-linux-amd64.rpm\`)。x86_64 用文件名带 amd64 的包,aarch64(统信 UOS、麒麟等)用带 arm64 的包。\n\n" "$windows"
|
||||
printf "**macOS** 已用 Developer ID 签名并公证,打开时不需要清除隔离属性。%s**Linux** Debian 系从 \`.deb\` 安装,Fedora、RHEL、openSUSE 等从 \`.rpm\` 安装(\`sudo dnf install ./ReasonixStudio-linux-amd64.rpm\`)。x86_64 用文件名带 amd64 的包,aarch64(统信 UOS、麒麟等)用带 arm64 的包。也提供 \`.AppImage\`(只提供下载,不会自更新;需要 FUSE 2,或用 \`--appimage-extract-and-run\` 启动)。\n\n" "$windows"
|
||||
printf "%s\n" "三个平台都能自更新:Linux 的 \`.deb\` 走自己的包(\`.rpm\` 的自更新尚未提供,请手动下载新包),Windows 运行下一个安装器,macOS 替换自身 bundle。"
|
||||
} >> "$notes"
|
||||
assets=()
|
||||
|
||||
@@ -363,7 +363,7 @@ jobs:
|
||||
arm64=false
|
||||
platform=$(git diff -z --name-only --no-renames "$BASE_SHA" HEAD | bash scripts/ci-platform-scope.sh)
|
||||
if git diff --name-only --no-renames "$BASE_SHA" HEAD |
|
||||
grep -qE '^(desktop/electron/|desktop/packaging/|desktop/cmd/update-helper/|desktop/cmd/studio-manifest/|desktop/go\.(mod|sum)$|cmd/reasonix-studio-host/|internal/platform/(update|appupdate)/|go\.(mod|sum)$|scripts/(linux-install-smoke|verify-linux-package|verify-studio-artifacts)\.sh$|scripts/(windows-signing-lib|verify-windows-package|verify-windows-authenticode|install-smoke)\.ps1$|\.github/workflows/(studio|release-studio)\.yml$)'; then
|
||||
grep -qE '^(desktop/electron/|desktop/packaging/|desktop/cmd/update-helper/|desktop/cmd/studio-manifest/|desktop/go\.(mod|sum)$|cmd/reasonix-studio-host/|internal/platform/(update|appupdate)/|go\.(mod|sum)$|scripts/(linux-install-smoke|verify-linux-package|verify-linux-appimage|verify-studio-artifacts)\.sh$|scripts/(windows-signing-lib|verify-windows-package|verify-windows-authenticode|install-smoke)\.ps1$|\.github/workflows/(studio|release-studio)\.yml$)'; then
|
||||
arm64=true
|
||||
fi
|
||||
fi
|
||||
@@ -607,6 +607,10 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: bash scripts/linux-install-smoke.sh "dist/ReasonixStudio-linux-${{ matrix.arch }}.deb" "${{ matrix.arch }}" 0.0.0-ci
|
||||
|
||||
- name: The AppImage carries its architecture, the kernel and the page
|
||||
if: runner.os == 'Linux'
|
||||
run: bash scripts/verify-linux-appimage.sh "dist/ReasonixStudio-linux-${{ matrix.arch }}.AppImage" "${{ matrix.arch }}"
|
||||
|
||||
# Recorded, not asserted: the real bundle layout, so later work argues
|
||||
# from what electron-builder produced rather than from its documentation.
|
||||
- name: Record the macOS bundle layout
|
||||
|
||||
@@ -151,7 +151,7 @@ pre-releases):
|
||||
| --- | --- | --- |
|
||||
| macOS | `.dmg` or `.zip` | Apple Silicon (`arm64`) / Intel (`amd64`) |
|
||||
| Windows | Installer `-installer.exe` or portable `.zip` | x64 |
|
||||
| Linux | `.deb` or `.rpm` | x64 (`amd64`) / ARM64 (`arm64`, aarch64) |
|
||||
| Linux | `.deb`, `.rpm`, or `.AppImage` (download only) | x64 (`amd64`) / ARM64 (`arm64`, aarch64) |
|
||||
|
||||
Every package ships with a `.minisig` signature, and the release carries
|
||||
`SHA256SUMS`. Once installed, Studio updates itself in place.
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ Reasonix 分为两条版本线,调整的原因见[版本路线公告](https://
|
||||
| --- | --- | --- |
|
||||
| macOS | `.dmg` 或 `.zip` | Apple Silicon(`arm64`)/ Intel(`amd64`) |
|
||||
| Windows | 安装器 `-installer.exe` 或便携 `.zip` | x64 |
|
||||
| Linux | `.deb` 或 `.rpm` | x64(`amd64`)/ ARM64(`arm64`,aarch64) |
|
||||
| Linux | `.deb`、`.rpm` 或 `.AppImage`(只提供下载) | x64(`amd64`)/ ARM64(`arm64`,aarch64) |
|
||||
|
||||
每个安装包都附带 `.minisig` 签名,发布页另有 `SHA256SUMS`。安装后 Studio 会在
|
||||
应用内自行更新。
|
||||
|
||||
@@ -135,14 +135,16 @@ func releaseAsset(repo, tag, name string, mirrored bool) update.Asset {
|
||||
}
|
||||
}
|
||||
|
||||
// installable reports whether an artifact installs itself rather than expecting
|
||||
// the reader to place it: a disk image to drag from, an installer to run, a
|
||||
// package for dpkg.
|
||||
// installable reports whether an artifact is a download a person is offered: a
|
||||
// disk image to drag from, an installer to run, a package for dpkg or rpm, or an
|
||||
// AppImage to run in place. The AppImage is a download only; no native_packages
|
||||
// key names it, so no client replaces one.
|
||||
func installable(name string) bool {
|
||||
return strings.HasSuffix(name, ".dmg") ||
|
||||
strings.HasSuffix(name, "-installer.exe") ||
|
||||
strings.HasSuffix(name, ".deb") ||
|
||||
strings.HasSuffix(name, ".rpm")
|
||||
strings.HasSuffix(name, ".rpm") ||
|
||||
strings.HasSuffix(name, ".AppImage")
|
||||
}
|
||||
|
||||
// macArchiveKeys maps a macOS archive to the platforms it can install. The
|
||||
|
||||
@@ -340,3 +340,36 @@ func TestLinuxArm64PackagesAreKeyedBesideAmd64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An AppImage is a download a person is offered and nothing an updater
|
||||
// replaces: it must not appear as a platform asset or a native package, and
|
||||
// adding it must not move any entry a shipped client reads.
|
||||
func TestAnAppImageIsOfferedButNeverResolvedAsAnUpdate(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, name := range []string{"ReasonixStudio-linux-amd64.deb", "ReasonixStudio-linux-amd64.AppImage", "ReasonixStudio-linux-arm64.AppImage"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(name), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Setenv("GITHUB_REPOSITORY", "esengine/DeepSeek-Reasonix")
|
||||
if err := run(dir, "v0.1.0", "studio-v0.1.0", "", true); err != nil {
|
||||
t.Fatalf("run: %v", err)
|
||||
}
|
||||
var m update.Manifest
|
||||
raw, _ := os.ReadFile(filepath.Join(dir, "latest.json"))
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range []string{"ReasonixStudio-linux-amd64.AppImage", "ReasonixStudio-linux-arm64.AppImage"} {
|
||||
a, ok := m.Downloads[name]
|
||||
if !ok || a.Sig != a.URL+".minisig" {
|
||||
t.Errorf("downloads[%s] = %+v, %v; want a signed download", name, a, ok)
|
||||
}
|
||||
}
|
||||
if len(m.NativePackages) != 1 || !strings.HasSuffix(m.NativePackages["linux-amd64"].URL, "linux-amd64.deb") {
|
||||
t.Errorf("native packages = %+v, want only the .deb", m.NativePackages)
|
||||
}
|
||||
if len(m.Platforms) != 0 {
|
||||
t.Errorf("platforms = %+v: an AppImage must not resolve as an update", m.Platforms)
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,8 @@ linux:
|
||||
target:
|
||||
- deb
|
||||
- rpm
|
||||
# Download only: it does not update itself, and nothing here installs it.
|
||||
- AppImage
|
||||
icon: assets/icon.png
|
||||
category: Development
|
||||
maintainer: esengine <[email protected]>
|
||||
|
||||
@@ -288,7 +288,8 @@ Studio and 1.x signing jobs and smoke tests share the concurrency group `certum-
|
||||
`latest.json` keys `platforms` (and `deltas`) as `<GOOS>-<GOARCH>`, derived from the artifact name by `studio-manifest` and asked for by the running kernel through `runtime.GOOS` and `runtime.GOARCH`.
|
||||
|
||||
- The key is the binary's own architecture, not the machine's. An x64 build running under ARM64 emulation asks for `windows-amd64` and keeps updating to the amd64 installer; it is not moved to a native build.
|
||||
- Linux ships a `.deb` and an `.rpm` for each of amd64 and arm64 (aarch64), all named `ReasonixStudio-linux-<arch>.<ext>`; the publish job requires all four by name.
|
||||
- Linux ships a `.deb`, an `.rpm` and an `.AppImage` for each of amd64 and arm64 (aarch64), all named `ReasonixStudio-linux-<arch>.<ext>`; the publish job requires all six by name.
|
||||
- The `.AppImage` is a download only: no `native_packages` key names it and no client replaces one, so an AppImage user downloads the next one. `scripts/verify-linux-appimage.sh` checks its type marker, its ELF machine, and that extracting it yields the kernel (for the right architecture) and the page.
|
||||
- CI and releases use `scripts/verify-linux-package.sh` to read both formats back: package name, architecture, root-owned update payload, every ELF image, and static Go binaries.
|
||||
- `scripts/linux-install-smoke.sh` runs only with `GITHUB_ACTIONS=true`. It installs and reinstalls the `.deb`, drives its packaged Electron window under Xvfb through a renderer-to-kernel request and second launch, checks normal shell/kernel exit, and uninstalls.
|
||||
- ARM64 packaging does not establish compatibility with every UOS/Kylin version. Qualify the target distribution's glibc, GTK/NSS, graphics/session environment and, for `.deb` self-update, apt, pkexec and Polkit. RPM updates remain manual.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
- 新增:Linux 增加 `.AppImage` 下载(amd64 和 arm64),可直接运行;它不会自更新,新版本需重新下载。关联 #11653。
|
||||
- Added: Linux gets an `.AppImage` download for amd64 and arm64 that runs in place. It does not update itself: download the next release. Refs #11653.
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
appimage=${1:?usage: verify-linux-appimage.sh APPIMAGE ARCH}
|
||||
arch=${2:?usage: verify-linux-appimage.sh APPIMAGE ARCH}
|
||||
case "$arch" in
|
||||
amd64) machine=62 ;;
|
||||
arm64) machine=183 ;;
|
||||
*) echo "unsupported architecture: $arch" >&2; exit 1 ;;
|
||||
esac
|
||||
test -s "$appimage"
|
||||
|
||||
# An AppImage is an ELF runtime with a type marker at offset 8: "AI" and the type
|
||||
# byte 2, the squashfs flavour. Offset 18 is the ELF machine, little-endian.
|
||||
test "$(dd if="$appimage" bs=1 count=4 2>/dev/null | od -An -tx1 | tr -d ' \n')" = 7f454c46
|
||||
test "$(dd if="$appimage" bs=1 skip=8 count=3 2>/dev/null | od -An -tx1 | tr -d ' \n')" = 414902
|
||||
test "$(dd if="$appimage" bs=1 skip=18 count=1 2>/dev/null | od -An -tu1 | tr -d ' \n')" = "$machine"
|
||||
|
||||
stage=$(mktemp -d)
|
||||
trap 'rm -rf -- "$stage"' EXIT
|
||||
cp "$appimage" "$stage/app"
|
||||
chmod +x "$stage/app"
|
||||
# Extracting reads the image itself, so it needs neither FUSE nor a display.
|
||||
(cd "$stage" && ./app --appimage-extract >/dev/null)
|
||||
root=$stage/squashfs-root
|
||||
host=$(find "$root" -type f -path '*/bin/reasonix-studio-host' ! -path '*app.asar*' | head -1)
|
||||
page=$(find "$root" -type f -path '*/frontend-next/dist/index.html' ! -path '*app.asar*' | head -1)
|
||||
test -n "$host" -a -x "$host"
|
||||
test -n "$page"
|
||||
test "$(dd if="$host" bs=1 skip=18 count=1 2>/dev/null | od -An -tu1 | tr -d ' \n')" = "$machine"
|
||||
test -e "$root/AppRun"
|
||||
echo "AppImage ok: kernel ${host#"$root"/}, page ${page#"$root"/}"
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
dir=${1:?usage: verify-studio-artifacts.sh DIRECTORY}
|
||||
for arch in amd64 arm64; do
|
||||
for name in "ReasonixStudio-linux-$arch.deb" "ReasonixStudio-linux-$arch.rpm" \
|
||||
for name in "ReasonixStudio-linux-$arch.deb" "ReasonixStudio-linux-$arch.rpm" "ReasonixStudio-linux-$arch.AppImage" \
|
||||
"ReasonixStudio-windows-$arch-installer.exe" "ReasonixStudio-windows-$arch.zip" \
|
||||
"ReasonixStudio-darwin-$arch.dmg" "ReasonixStudio-darwin-$arch.zip"; do
|
||||
test -s "$dir/$name" || { echo "missing or empty Studio artifact: $name" >&2; exit 1; }
|
||||
|
||||
Reference in new issue
Block a user