Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
168 changes: 158 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,58 @@ jobs:
- name: Package app bundle with standardized name (unix)
if: runner.os != 'Windows'
shell: bash
env:
MATRIX_PLATFORM: ${{ matrix.platform }}
MATRIX_TARGET: ${{ matrix.target }}
GITHUB_REF: ${{ github.ref }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
IFS='/' read -r os arch <<< "${{ matrix.platform }}"
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
version="${{ github.ref_name }}"; version="${version#v}"
IFS='/' read -r os arch <<< "$MATRIX_PLATFORM"
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
version="$GITHUB_REF_NAME"; version="${version#v}"
else
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
bundle_dir="src-tauri/target/$MATRIX_TARGET/release/bundle"
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.
if [[ "$os" == "darwin" ]]; then
sig=$(find "$bundle_dir" -name '*.app.tar.gz.sig' -type f | head -1)
elif [[ "$os" == "linux" ]]; then
sig=$(find "$bundle_dir" -name '*.AppImage.tar.gz.sig' -type f | head -1)
else
sig=$(find "$bundle_dir" -name '*.sig' -type f | head -1)
fi
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.tar.gz) ext="AppImage.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"
if [[ "$os" == "linux" ]]; then
appimage=$(find "$bundle_dir/appimage" -name "*.AppImage" -type f | head -1)
if [[ -z "$appimage" ]]; then
echo "::error::No AppImage under $bundle_dir/appimage"
find "$bundle_dir" -maxdepth 3 -type f | sed 's/^/found: /' || true
exit 1
fi
plain_asset="Risuko_${version}_${os}_${arch}.AppImage"
cp "$appimage" "$plain_asset"
echo "PLAIN_APPIMAGE_ASSET=$plain_asset" >> "$GITHUB_ENV"
fi
elif [[ "$os" == "darwin" ]]; then
asset="Risuko_${version}_${os}_${arch}.app.tar.gz"
tar czf "$asset" -C "$bundle_dir/macos" "Risuko.app"
else
Expand All @@ -276,18 +318,37 @@ jobs:
- name: Package app bundle with standardized name (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
MATRIX_PLATFORM: ${{ matrix.platform }}
MATRIX_TARGET: ${{ matrix.target }}
GITHUB_REF: ${{ github.ref }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
$parts = "${{ matrix.platform }}" -split "/"
$parts = $env:MATRIX_PLATFORM -split "/"
$os = $parts[0]; $arch = $parts[1]
if ("${{ github.ref }}" -match "^refs/tags/") {
$version = "${{ github.ref_name }}" -replace "^v", ""
if ($env:GITHUB_REF -match "^refs/tags/") {
$version = $env:GITHUB_REF_NAME -replace "^v", ""
} else {
$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/$env:MATRIX_TARGET/release/bundle/nsis"
$asset = "Risuko_${version}_${os}_${arch}.setup.exe"
Copy-Item $setup.FullName $asset
if ($env: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 +358,45 @@ jobs:
set -euo pipefail
node scripts/write-sha256-sidecar.mjs "$APP_ASSET"

- name: Write plain Linux AppImage SHA-256 sidecar
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/') && env.PLAIN_APPIMAGE_ASSET != ''
shell: bash
run: |
set -euo pipefail
node scripts/write-sha256-sidecar.mjs "$PLAIN_APPIMAGE_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
env:
MATRIX_TARGET: ${{ matrix.target }}
APP_ASSET: ${{ env.APP_ASSET }}
UPDATER_SIG_FILE: ${{ env.UPDATER_SIG_FILE }}
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"

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

- name: Upload standardized app bundle to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
Expand All @@ -305,6 +405,18 @@ jobs:
files: |
${{ env.APP_ASSET }}
${{ env.APP_ASSET }}.sha256
${{ env.APP_ASSET }}.sig
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload plain Linux AppImage to GitHub Release
if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/') && env.PLAIN_APPIMAGE_ASSET != ''
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
${{ env.PLAIN_APPIMAGE_ASSET }}
${{ env.PLAIN_APPIMAGE_ASSET }}.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down Expand Up @@ -440,3 +552,39 @@ 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@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false

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

- name: Download updater fragments
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
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
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${{ github.ref_name }}" latest.json --clobber --repo "${{ github.repository }}"
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
43 changes: 43 additions & 0 deletions scripts/build-updater-manifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node


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

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 = Object.create(null);
for (const file of readdirSync(dir).sort()) {
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}`);
}
if (Object.hasOwn(platforms, frag.key)) {
throw new Error(`duplicate fragment key: ${frag.key}`);
}
platforms[frag.key] = { signature: frag.signature, url: `${base}/${frag.asset}` };
}
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 (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
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(", ")}`);
}
Loading
Loading