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
106 changes: 103 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,26 @@ jobs:
version=$(grep -m1 '^version' src-tauri/Cargo.toml | sed 's/.*"\(.*\)"/\1/')
fi
bundle_dir="src-tauri/target/${{ matrix.target }}/release/bundle"
if [[ "$os" == "darwin" ]]; then
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
# Serve tauri's exact updater artifact so its minisign .sig stays valid.
# Renaming is fine — minisign signs file *contents*, not the filename.
sig=$(find "$bundle_dir" -name '*.sig' -type f | head -1)
if [[ -z "$sig" ]]; then
echo "::error::No updater .sig under $bundle_dir. Is TAURI_SIGNING_PRIVATE_KEY set?"
find "$bundle_dir" -maxdepth 3 -type f | sed 's/^/found: /' || true
exit 1
fi
signed="${sig%.sig}"
case "$(basename "$signed")" in
*.app.tar.gz) ext="app.tar.gz" ;;
*.AppImage) ext="AppImage" ;;
*) ext="${signed##*.}" ;;
esac
asset="Risuko_${version}_${os}_${arch}.${ext}"
cp "$signed" "$asset"
cp "$sig" "$asset.sig"
echo "UPDATER_SIG_FILE=$asset.sig" >> "$GITHUB_ENV"
elif [[ "$os" == "darwin" ]]; then
asset="Risuko_${version}_${os}_${arch}.app.tar.gz"
tar czf "$asset" -C "$bundle_dir/macos" "Risuko.app"
else
Expand All @@ -285,9 +304,23 @@ jobs:
$cargo = Get-Content "src-tauri/Cargo.toml" -Raw
$version = [regex]::Match($cargo, '^version\s*=\s*"([^"]+)"', [System.Text.RegularExpressions.RegexOptions]::Multiline).Groups[1].Value
}
$setup = Get-ChildItem "src-tauri/target/${{ matrix.target }}/release/bundle/nsis" -Filter "*-setup.exe" | Select-Object -First 1
$nsis = "src-tauri/target/${{ matrix.target }}/release/bundle/nsis"
$asset = "Risuko_${version}_${os}_${arch}.setup.exe"
Copy-Item $setup.FullName $asset
if ("${{ github.ref }}" -match "^refs/tags/") {
# Serve tauri's exact signed installer so its minisign .sig stays valid.
$sig = Get-ChildItem $nsis -Filter "*-setup.exe.sig" | Select-Object -First 1
if (-not $sig) {
Write-Error "No updater .sig under $nsis (is TAURI_SIGNING_PRIVATE_KEY set?)"
exit 1
}
$signed = $sig.FullName -replace '\.sig$', ''
Copy-Item $signed $asset
Copy-Item $sig.FullName "$asset.sig"
"UPDATER_SIG_FILE=$asset.sig" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
} else {
$setup = Get-ChildItem $nsis -Filter "*-setup.exe" | Select-Object -First 1
Copy-Item $setup.FullName $asset
}
"APP_ASSET=$asset" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
"APP_PLATFORM_LABEL=${os}-${arch}" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8

Expand All @@ -297,6 +330,34 @@ jobs:
set -euo pipefail
node scripts/write-sha256-sidecar.mjs "$APP_ASSET"

# Record this platform's updater entry (key + asset + signature) for the
# aggregation job that assembles latest.json across all matrix jobs.
- name: Emit updater manifest fragment
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
set -euo pipefail
triple="${{ matrix.target }}"
arch="${triple%%-*}" # x86_64 | aarch64 | i686
case "$triple" in
*apple-darwin) tos=darwin ;;
*windows*) tos=windows ;;
*linux*) tos=linux ;;
*) echo "::error::unknown target triple $triple"; exit 1 ;;
esac
key="${tos}-${arch}"
signature=$(tr -d '\n' < "$UPDATER_SIG_FILE")
mkdir -p fragments
printf '{"key":"%s","asset":"%s","signature":"%s"}' "$key" "$APP_ASSET" "$signature" > "fragments/$key.json"
echo "fragment: fragments/${key}.json -> $APP_ASSET"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Upload updater fragment
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: updater-fragment-${{ matrix.target }}
path: fragments/*.json

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Upload standardized app bundle to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
Expand All @@ -305,6 +366,7 @@ jobs:
files: |
${{ env.APP_ASSET }}
${{ env.APP_ASSET }}.sha256
${{ env.APP_ASSET }}.sig
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down Expand Up @@ -440,3 +502,41 @@ jobs:
else
echo "DMG directory not found: $dmg_dir"
fi

updater-manifest:
name: Build updater latest.json
needs: release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24

- name: Download updater fragments
uses: actions/download-artifact@v4
with:
path: fragments
pattern: updater-fragment-*
merge-multiple: true

- name: Build latest.json
shell: bash
run: node scripts/build-updater-manifest.mjs fragments latest.json
env:
VERSION: ${{ github.ref_name }}
REPO: ${{ github.repository }}

- name: Upload latest.json to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: latest.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4 changes: 2 additions & 2 deletions README-CN.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Risuko

<p>
<a href="https://risuko.vercel.app">
<a href="https://risuko.app">
<img src="./static/logo.svg" width="256" alt="Risuko App Icon" />
</a>
</p>
Expand All @@ -17,7 +17,7 @@

Risuko 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链等资源。它的界面简洁易用,希望大家喜欢 👻。

✈️ 去 [官网](https://risuko.vercel.app) 逛逛
✈️ 去 [官网](https://risuko.app) 逛逛

## 💽 安装

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Risuko

<p>
<a href="https://risuko.vercel.app">
<a href="https://risuko.app">
<img src="./static/logo.svg" width="256" alt="Risuko App Icon" />
</a>
</p>
Expand All @@ -19,7 +19,7 @@ Risuko is a full-featured download manager that supports downloading HTTP, FTP,

Risuko has a clean and easy to use interface. I hope you will like it 👻.

✈️ [Official Website](https://risuko.vercel.app)
✈️ [Official Website](https://risuko.app)

## 💽 Installation

Expand Down
39 changes: 39 additions & 0 deletions scripts/build-updater-manifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env node


import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";

export function buildManifest(dir, { version, repo } = {}) {
version = String(version ?? "").replace(/^v/i, "");
if (!version) throw new Error("version required");
if (!repo) throw new Error("repo (owner/repo) required");
const base = `https://github.com/${repo}/releases/download/v${version}`;
const platforms = {};
for (const file of readdirSync(dir)) {
if (!file.endsWith(".json")) continue;
const frag = JSON.parse(readFileSync(join(dir, file), "utf8"));
if (!frag.key || !frag.asset || !frag.signature) {
throw new Error(`incomplete fragment: ${file}`);
}
platforms[frag.key] = { signature: frag.signature, url: `${base}/${frag.asset}` };
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
if (Object.keys(platforms).length === 0) throw new Error(`no fragments in ${dir}`);

return { version, notes: "", pub_date: new Date().toISOString(), platforms };
}

// CLI: node scripts/build-updater-manifest.mjs <fragments-dir> [out]
if (import.meta.url === `file://${process.argv[1]}`) {
const [dir, out = "latest.json"] = process.argv.slice(2);
if (!dir) {
console.error("Usage: build-updater-manifest.mjs <fragments-dir> [out]");
process.exit(2);
}
const manifest = buildManifest(dir, {
version: process.env.VERSION,
repo: process.env.REPO,
});
writeFileSync(out, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`wrote ${out}: ${Object.keys(manifest.platforms).join(", ")}`);
}
44 changes: 44 additions & 0 deletions scripts/build-updater-manifest.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildManifest } from "./build-updater-manifest.mjs";

const dir = mkdtempSync(join(tmpdir(), "frag-"));
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
writeFileSync(
join(dir, "darwin-aarch64.json"),
JSON.stringify({
key: "darwin-aarch64",
asset: "Risuko_1.2.3_darwin_arm64.app.tar.gz",
signature: "SIG_A",
}),
);
writeFileSync(
join(dir, "windows-x86_64.json"),
JSON.stringify({
key: "windows-x86_64",
asset: "Risuko_1.2.3_win32_x64.setup.exe",
signature: "SIG_W",
}),
);

const m = buildManifest(dir, {
version: "v1.2.3",
repo: "YueMiyuki/Risuko",
});

assert.equal(m.version, "1.2.3");
assert.match(m.pub_date, /^\d{4}-\d{2}-\d{2}T/);
assert.equal(m.platforms["darwin-aarch64"].signature, "SIG_A");
assert.equal(
m.platforms["darwin-aarch64"].url,
"https://github.com/YueMiyuki/Risuko/releases/download/v1.2.3/Risuko_1.2.3_darwin_arm64.app.tar.gz",
);
assert.equal(
m.platforms["windows-x86_64"].url,
"https://github.com/YueMiyuki/Risuko/releases/download/v1.2.3/Risuko_1.2.3_win32_x64.setup.exe",
);
assert.throws(() => buildManifest(dir, { repo: "x/y" }), /version required/);

rmSync(dir, { recursive: true, force: true });
console.log("ok");
Loading
Loading