diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..15c3948
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,78 @@
+name: Bug Report
+description: Something isn't working as expected
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to report a bug. The more detail you include, the faster it gets fixed.
+ You can use the **Report a Bug** button in **Settings → About** to pre-fill most of this automatically.
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: What's broken? A clear, concise summary.
+ placeholder: "e.g. Library card stats don't appear even with 'Always show' enabled"
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to Reproduce
+ description: Exact steps to trigger the bug.
+ placeholder: |
+ 1. Open Settings → Library
+ 2. Enable "Always show card stats"
+ 3. Return to Library
+ 4. Unread counts are not visible
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected Behavior
+ placeholder: "Unread and download counts should be permanently visible on manga cards"
+ validations:
+ required: true
+
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual Behavior
+ placeholder: "Counts only appear on hover, or not at all"
+ validations:
+ required: true
+
+ - type: textarea
+ id: environment
+ attributes:
+ label: Environment
+ description: Copy this from Settings → About → Report a Bug, or fill in manually.
+ placeholder: |
+ - Moku Version: v0.9.4
+ - Platform: Windows / macOS / Linux / Web
+ - OS Version: Windows 11 24H2
+ - Server: Suwayomi v2.2.2196
+ - Server URL: localhost:4567
+ validations:
+ required: true
+
+ - type: textarea
+ id: settings
+ attributes:
+ label: Relevant Settings
+ description: Settings related to the bug (auto-filled by the in-app reporter, or paste manually).
+ placeholder: |
+ libraryStatsAlways: true
+ libraryCropCovers: true
+ libraryPageSize: 48
+ render: yaml
+
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional Context
+ description: Screenshots, screen recordings, console errors, anything else helpful.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..52563c2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Discussions (Questions & Support)
+ url: https://github.com/moku-project/Moku/discussions
+ about: Not a bug? Ask questions and get help here.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..3eeead3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,47 @@
+name: Feature Request
+description: Suggest an improvement or new feature
+labels: ["enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Got an idea? Describe what you want and why it would be useful.
+
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem / Motivation
+ description: What's the gap or frustration this would address?
+ placeholder: "e.g. There's no way to bulk-mark chapters as read without opening each series"
+ validations:
+ required: true
+
+ - type: textarea
+ id: solution
+ attributes:
+ label: Proposed Solution
+ description: What would you like to see?
+ placeholder: "A 'Mark all read' option in the series long-press context menu"
+ validations:
+ required: true
+
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives Considered
+ description: Any workarounds you've tried, or other ways this could be solved.
+
+ - type: textarea
+ id: environment
+ attributes:
+ label: Environment
+ description: Optional — useful if this is platform-specific.
+ placeholder: |
+ - Moku Version: v0.9.4
+ - Platform: Windows / macOS / Linux / Web
+
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional Context
+ description: Mockups, references, examples from other apps, etc.
\ No newline at end of file
diff --git a/.github/read_versions.sh b/.github/read_versions.sh
new file mode 100644
index 0000000..913c89e
--- /dev/null
+++ b/.github/read_versions.sh
@@ -0,0 +1,18 @@
+# Sourced by CI jobs that need versions from nix/versions.nix.
+# Usage: source .github/read_versions.sh
+# Exports: MOKU_VERSION SUWA_VERSION SUWA_HASH_LINUX SUWA_HASH_MACOS_ARM64 SUWA_HASH_MACOS_X64 SUWA_HASH_WINDOWS
+
+_nix="$( cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd )/nix/versions.nix"
+_t=$(cat "$_nix")
+
+_pick() { echo "$_t" | grep -oP "${1}\s*=\s*\"\K[^\"]+"; }
+
+export MOKU_VERSION=$(_pick "moku")
+export SUWA_VERSION=$(_pick "version")
+export SUWA_HASH_WINDOWS=$(_pick "windowsHash")
+export SUWA_HASH_LINUX=$(_pick "linuxHash")
+export SUWA_HASH_MACOS_ARM64=$(_pick "macosArm64Hash")
+export SUWA_HASH_MACOS_X64=$(_pick "macosX64Hash")
+
+unset _nix _t
+unset -f _pick
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index 06b9c7f..b7fb699 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -16,24 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Build
- run: pnpm build
-
- - name: Upload dist
- uses: actions/upload-artifact@v4
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm build:static
+ - uses: actions/upload-artifact@v4
with:
name: frontend-dist-linux
path: dist/
@@ -43,77 +32,54 @@ jobs:
name: Tauri (Linux x64)
needs: frontend
runs-on: ubuntu-22.04
-
steps:
- uses: actions/checkout@v4
- - name: Download frontend dist
- uses: actions/download-artifact@v4
- with:
- name: frontend-dist-linux
- path: dist/
+ - uses: actions/download-artifact@v4
+ with: { name: frontend-dist-linux, path: dist/ }
+
+ - name: Read versions
+ run: |
+ source .github/read_versions.sh
+ echo "MOKU_VERSION=$MOKU_VERSION" >> $GITHUB_ENV
+ echo "SUWA_VERSION=$SUWA_VERSION" >> $GITHUB_ENV
+ echo "SUWA_HASH=$SUWA_HASH_LINUX" >> $GITHUB_ENV
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
- libwebkit2gtk-4.1-dev \
- libappindicator3-dev \
- librsvg2-dev \
- patchelf \
- libfuse2
+ libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libfuse2
- - name: Install Rust
- uses: dtolnay/rust-toolchain@stable
- with:
- targets: x86_64-unknown-linux-gnu
+ - uses: dtolnay/rust-toolchain@stable
+ with: { targets: x86_64-unknown-linux-gnu }
- - name: Rust cache
- uses: Swatinem/rust-cache@v2
- with:
- workspaces: src-tauri
+ - uses: Swatinem/rust-cache@v2
+ with: { workspaces: src-tauri }
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
- name: Download Suwayomi (Linux x64)
run: |
curl -fsSL \
- "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v2.2.2196/Suwayomi-Server-v2.2.2196-linux-x64.tar.gz" \
+ "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VERSION}/Suwayomi-Server-v${SUWA_VERSION}-linux-x64.tar.gz" \
-o suwayomi-linux.tar.gz
-
- echo "e13d63ceb7e2b15e83d0a78281e8c1c04ac4a833caa73e5a2b68fbaf0cb20c1f suwayomi-linux.tar.gz" | sha256sum -c -
-
+ echo "${SUWA_HASH} suwayomi-linux.tar.gz" | sha256sum -c -
mkdir -p suwayomi-extracted
tar -xzf suwayomi-linux.tar.gz -C suwayomi-extracted --strip-components=1
- name: Stage Suwayomi bundle
run: |
mkdir -p src-tauri/binaries
-
- JAR="suwayomi-extracted/bin/Suwayomi-Server.jar"
- JAVA="suwayomi-extracted/jre/bin/java"
- CATCH="suwayomi-extracted/bin/catch_abort.so"
-
- for f in "$JAR" "$JAVA" "$CATCH"; do
- if [ ! -e "$f" ]; then
- echo "ERROR: expected file not found: $f"
- find suwayomi-extracted -type f | head -40
- exit 1
- fi
+ for f in suwayomi-extracted/bin/Suwayomi-Server.jar \
+ suwayomi-extracted/jre/bin/java \
+ suwayomi-extracted/bin/catch_abort.so; do
+ [ -e "$f" ] || { echo "ERROR: missing $f"; find suwayomi-extracted -type f | head -40; exit 1; }
done
-
- echo "JAR=$JAR JAVA=$JAVA CATCH=$CATCH"
-
cp -r suwayomi-extracted src-tauri/binaries/suwayomi-bundle
chmod +x src-tauri/binaries/suwayomi-bundle/jre/bin/java
@@ -124,48 +90,34 @@ jobs:
chmod +x src-tauri/binaries/suwayomi-launcher-linux-x86_64-unknown-linux-gnu
- name: Patch tauri.conf.json for CI
- run: |
- sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
+ run: sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
- name: Build Tauri app
run: pnpm tauri build --target x86_64-unknown-linux-gnu --config src-tauri/tauri.linux.conf.json --verbose
- env:
- NO_STRIP: "true"
+ env: { NO_STRIP: "true" }
- name: Upload Linux artifacts to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- VERSION: ${{ github.event.inputs.version }}
run: |
for i in $(seq 1 12); do
RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/moku-project/Moku/releases" \
- | jq -r '.[] | select(.tag_name == "v'"$VERSION"'") | .id' | head -1)
- if [ -n "$RELEASE_ID" ]; then break; fi
- echo "Waiting for release to exist... attempt $i"
- sleep 15
+ | jq -r '.[] | select(.tag_name == "v${{ github.event.inputs.version }}") | .id' | head -1)
+ [ -n "$RELEASE_ID" ] && break
+ echo "Waiting for release... attempt $i"; sleep 15
done
+ [ -z "$RELEASE_ID" ] && { echo "ERROR: release not found"; exit 1; }
- if [ -z "$RELEASE_ID" ]; then
- echo "ERROR: Could not find release for v$VERSION after waiting"
- exit 1
- fi
-
- echo "Found release ID: $RELEASE_ID"
-
- upload_asset() {
- local file="$1"
- local name="$2"
- echo "Uploading $name..."
+ upload() {
curl -s -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
- --data-binary @"$file" \
- "https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=$name"
+ --data-binary @"$1" \
+ "https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=$2"
}
APPIMAGE=$(find src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage -name "*.AppImage" | head -1)
DEB=$(find src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb -name "*.deb" | head -1)
-
- [ -n "$APPIMAGE" ] && upload_asset "$APPIMAGE" "moku-linux-x64-${VERSION}.AppImage"
- [ -n "$DEB" ] && upload_asset "$DEB" "moku-linux-x64-${VERSION}.deb"
\ No newline at end of file
+ [ -n "$APPIMAGE" ] && upload "$APPIMAGE" "moku-linux-x64-${{ github.event.inputs.version }}.AppImage"
+ [ -n "$DEB" ] && upload "$DEB" "moku-linux-x64-${{ github.event.inputs.version }}.deb"
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
index 2e0d621..8769f06 100644
--- a/.github/workflows/build-macos.yml
+++ b/.github/workflows/build-macos.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
- description: "Version to build (e.g. 0.4.0)"
+ description: "Version to build (e.g. 0.9.0)"
required: true
permissions:
@@ -16,28 +16,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Build
- run: pnpm build
-
- - name: Upload dist
- uses: actions/upload-artifact@v4
- with:
- name: frontend-dist
- path: dist/
- retention-days: 1
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm build:static
+ - uses: actions/upload-artifact@v4
+ with: { name: frontend-dist, path: dist/, retention-days: 1 }
tauri:
name: Tauri (macOS)
@@ -46,149 +32,99 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Download frontend dist
- uses: actions/download-artifact@v4
- with:
- name: frontend-dist
- path: dist/
+ - uses: actions/download-artifact@v4
+ with: { name: frontend-dist, path: dist/ }
- - name: Install Rust
- uses: dtolnay/rust-toolchain@stable
- with:
- targets: aarch64-apple-darwin,x86_64-apple-darwin
+ - name: Read versions
+ run: |
+ source .github/read_versions.sh
+ echo "SUWA_VERSION=$SUWA_VERSION" >> $GITHUB_ENV
+ echo "SUWA_HASH_ARM64=$SUWA_HASH_MACOS_ARM64" >> $GITHUB_ENV
+ echo "SUWA_HASH_X64=$SUWA_HASH_MACOS_X64" >> $GITHUB_ENV
- - name: Rust cache
- uses: Swatinem/rust-cache@v2
- with:
- workspaces: src-tauri
+ - uses: dtolnay/rust-toolchain@stable
+ with: { targets: aarch64-apple-darwin,x86_64-apple-darwin }
+
+ - uses: Swatinem/rust-cache@v2
+ with: { workspaces: src-tauri }
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
- name: Download Suwayomi binaries
run: |
- download_suwayomi() {
+ dl() {
local asset="$1" sha="$2" outdir="$3"
curl -fsSL \
- "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v2.2.2196/${asset}" \
+ "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VERSION}/${asset}" \
-o "${outdir}.tar.gz"
echo "${sha} ${outdir}.tar.gz" | shasum -a 256 -c -
mkdir -p "${outdir}"
tar -xzf "${outdir}.tar.gz" -C "${outdir}" --strip-components=1
}
-
- download_suwayomi \
- "Suwayomi-Server-v2.2.2196-macOS-arm64.tar.gz" \
- "9e3dbebc7475707e8d11c56a473385c00b09bde0103d013bc1cb3d06c89e5c43" \
- "suwayomi-arm64"
-
- download_suwayomi \
- "Suwayomi-Server-v2.2.2196-macOS-x64.tar.gz" \
- "eadee02060b780a5febfb8dada2f89c7bd7db5905cfd20d47eaca02fcde8c9c5" \
- "suwayomi-x64"
+ dl "Suwayomi-Server-v${SUWA_VERSION}-macOS-arm64.tar.gz" "$SUWA_HASH_ARM64" suwayomi-arm64
+ dl "Suwayomi-Server-v${SUWA_VERSION}-macOS-x64.tar.gz" "$SUWA_HASH_X64" suwayomi-x64
- name: Stage Suwayomi sidecars
run: |
mkdir -p src-tauri/binaries
-
- stage_arch() {
- local srcdir="$1"
- local arch="$2"
- local sidecar="src-tauri/binaries/suwayomi-server-${arch}"
- local bundle_dest="src-tauri/binaries/suwayomi-bundle-${arch}"
-
+ stage() {
+ local srcdir="$1" arch="$2"
JAR=$(find "$srcdir" -name "Suwayomi-Server.jar" | head -1)
JAVA=$(find "$srcdir" -path "*/jre/bin/java" | head -1)
-
- if [ -z "$JAR" ]; then
- echo "ERROR: Suwayomi-Server.jar not found in $srcdir"
- find "$srcdir" -type f | head -30
- exit 1
- fi
- if [ -z "$JAVA" ]; then
- echo "ERROR: jre/bin/java not found in $srcdir"
- find "$srcdir" -type f | head -30
- exit 1
- fi
-
- echo "${arch}: jar=${JAR} java=${JAVA}"
-
- cp -r "$srcdir" "$bundle_dest"
-
- # The launcher script is committed at src-tauri/binaries/suwayomi-launcher.sh
- # to avoid embedding a heredoc in YAML (which breaks GitHub Actions parsing).
- cp src-tauri/binaries/suwayomi-launcher.sh "$sidecar"
- chmod +x "$sidecar"
- echo "Staged sidecar: $sidecar"
+ [ -z "$JAR" ] && { echo "ERROR: jar not found in $srcdir"; find "$srcdir" -type f | head -30; exit 1; }
+ [ -z "$JAVA" ] && { echo "ERROR: java not found in $srcdir"; find "$srcdir" -type f | head -30; exit 1; }
+ cp -r "$srcdir" "src-tauri/binaries/suwayomi-bundle-${arch}"
+ cp src-tauri/binaries/suwayomi-launcher.sh "src-tauri/binaries/suwayomi-server-${arch}"
+ chmod +x "src-tauri/binaries/suwayomi-server-${arch}"
}
-
- stage_arch suwayomi-arm64 aarch64-apple-darwin
- stage_arch suwayomi-x64 x86_64-apple-darwin
+ stage suwayomi-arm64 aarch64-apple-darwin
+ stage suwayomi-x64 x86_64-apple-darwin
- name: Patch tauri.conf.json for CI
- run: |
- sed -i '' 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
-
- - name: Swap bundle for aarch64
- run: |
- rm -rf src-tauri/binaries/suwayomi-bundle
- cp -r src-tauri/binaries/suwayomi-bundle-aarch64-apple-darwin \
- src-tauri/binaries/suwayomi-bundle
+ run: sed -i '' 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
- name: Build Tauri app (aarch64)
- run: pnpm tauri build --target aarch64-apple-darwin --config src-tauri/tauri.macos.conf.json
+ run: |
+ rm -rf src-tauri/binaries/suwayomi-bundle
+ cp -r src-tauri/binaries/suwayomi-bundle-aarch64-apple-darwin src-tauri/binaries/suwayomi-bundle
+ pnpm tauri build --target aarch64-apple-darwin --config src-tauri/tauri.macos.conf.json
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY || '-' }}
- - name: Swap bundle for x86_64
+ - name: Build Tauri app (x86_64)
run: |
rm -rf src-tauri/binaries/suwayomi-bundle
- cp -r src-tauri/binaries/suwayomi-bundle-x86_64-apple-darwin \
- src-tauri/binaries/suwayomi-bundle
-
- - name: Build Tauri app (x86_64)
- run: pnpm tauri build --target x86_64-apple-darwin --config src-tauri/tauri.macos.conf.json
+ cp -r src-tauri/binaries/suwayomi-bundle-x86_64-apple-darwin src-tauri/binaries/suwayomi-bundle
+ pnpm tauri build --target x86_64-apple-darwin --config src-tauri/tauri.macos.conf.json
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY || '-' }}
- name: Upload macOS artifacts to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- VERSION: ${{ github.event.inputs.version }}
run: |
- # Wait for the Windows workflow to have created the draft release
for i in $(seq 1 12); do
- RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/moku-project/Moku/releases" | jq -r '.[] | select(.tag_name == "v'"$VERSION"'") | .id' | head -1)
- if [ -n "$RELEASE_ID" ]; then break; fi
- echo "Waiting for release to exist... attempt $i"
- sleep 15
+ RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
+ "https://api.github.com/repos/moku-project/Moku/releases" \
+ | jq -r '.[] | select(.tag_name == "v${{ github.event.inputs.version }}") | .id' | head -1)
+ [ -n "$RELEASE_ID" ] && break
+ echo "Waiting for release... attempt $i"; sleep 15
done
+ [ -z "$RELEASE_ID" ] && { echo "ERROR: release not found"; exit 1; }
- if [ -z "$RELEASE_ID" ]; then
- echo "ERROR: Could not find release for v$VERSION after waiting"
- exit 1
- fi
-
- echo "Found release ID: $RELEASE_ID"
-
- upload_asset() {
- local file="$1"
- local name="$2"
- echo "Uploading $name..."
- curl -s -X POST -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" --data-binary @"$file" "https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=$name"
+ upload() {
+ curl -s -X POST \
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
+ -H "Content-Type: application/octet-stream" \
+ --data-binary @"$1" \
+ "https://uploads.github.com/repos/moku-project/Moku/releases/$RELEASE_ID/assets?name=$2"
}
- ARM64_DMG=$(find src-tauri/target/aarch64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1)
- X64_DMG=$(find src-tauri/target/x86_64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1)
-
- [ -n "$ARM64_DMG" ] && upload_asset "$ARM64_DMG" "moku-macos-arm64-${VERSION}.dmg"
- [ -n "$X64_DMG" ] && upload_asset "$X64_DMG" "moku-macos-x64-${VERSION}.dmg"
\ No newline at end of file
+ ARM64=$(find src-tauri/target/aarch64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1)
+ X64=$(find src-tauri/target/x86_64-apple-darwin/release/bundle/dmg -name "*.dmg" | head -1)
+ [ -n "$ARM64" ] && upload "$ARM64" "moku-macos-arm64-${{ github.event.inputs.version }}.dmg"
+ [ -n "$X64" ] && upload "$X64" "moku-macos-x64-${{ github.event.inputs.version }}.dmg"
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
index fd40f80..0dcb52a 100644
--- a/.github/workflows/build-windows.yml
+++ b/.github/workflows/build-windows.yml
@@ -16,152 +16,102 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Build
- run: pnpm build
-
- - name: Upload dist
- uses: actions/upload-artifact@v4
- with:
- name: frontend-dist-windows
- path: dist/
- retention-days: 1
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm build:static
+ - uses: actions/upload-artifact@v4
+ with: { name: frontend-dist-windows, path: dist/, retention-days: 1 }
tauri:
name: Tauri (Windows x64)
needs: frontend
runs-on: windows-latest
-
steps:
- uses: actions/checkout@v4
- - name: Download frontend dist
- uses: actions/download-artifact@v4
- with:
- name: frontend-dist-windows
- path: dist/
+ - uses: actions/download-artifact@v4
+ with: { name: frontend-dist-windows, path: dist/ }
- - name: Install Rust
- uses: dtolnay/rust-toolchain@stable
- with:
- targets: x86_64-pc-windows-msvc
+ - name: Read versions
+ shell: bash
+ run: |
+ source .github/read_versions.sh
+ echo "SUWA_VERSION=$SUWA_VERSION" >> $GITHUB_ENV
+ echo "SUWA_HASH=$SUWA_HASH_WINDOWS" >> $GITHUB_ENV
- - name: Rust cache
- uses: Swatinem/rust-cache@v2
- with:
- workspaces: src-tauri
+ - uses: dtolnay/rust-toolchain@stable
+ with: { targets: x86_64-pc-windows-msvc }
+
+ - uses: Swatinem/rust-cache@v2
+ with: { workspaces: src-tauri }
- uses: pnpm/action-setup@v4
- with:
- version: latest
-
+ with: { version: latest }
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: pnpm
-
- - name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ with: { node-version: 22, cache: pnpm }
+ - run: pnpm install --frozen-lockfile
- name: Download Suwayomi (Windows x64)
shell: bash
run: |
curl -fsSL \
- "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v2.2.2196/Suwayomi-Server-v2.2.2196-windows-x64.zip" \
+ "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VERSION}/Suwayomi-Server-v${SUWA_VERSION}-windows-x64.zip" \
-o suwayomi-windows.zip
- echo "457ca4a64a57e0d274a87203d25e962103bcb456ee30ada3ea47328a3093329d suwayomi-windows.zip" | sha256sum -c -
+ echo "${SUWA_HASH} suwayomi-windows.zip" | sha256sum -c -
unzip -q suwayomi-windows.zip -d suwayomi-raw
- - name: Extract Suwayomi bundle
+ - name: Stage Suwayomi bundle
shell: bash
run: |
mkdir -p suwayomi-extracted
TOP_DIRS=$(find suwayomi-raw -mindepth 1 -maxdepth 1 -type d | wc -l)
TOP_FILES=$(find suwayomi-raw -mindepth 1 -maxdepth 1 -type f | wc -l)
if [ "$TOP_DIRS" -eq 1 ] && [ "$TOP_FILES" -eq 0 ]; then
- INNER=$(find suwayomi-raw -mindepth 1 -maxdepth 1 -type d | head -1)
- cp -r "$INNER"/. suwayomi-extracted/
+ cp -r "$(find suwayomi-raw -mindepth 1 -maxdepth 1 -type d | head -1)"/. suwayomi-extracted/
else
cp -r suwayomi-raw/. suwayomi-extracted/
fi
-
- - name: Stage Suwayomi bundle
- shell: bash
- run: |
mkdir -p src-tauri/binaries
- JAVA=$(find suwayomi-extracted -path "*/jre/bin/java.exe" | head -1)
- JAR=$(find suwayomi-extracted -name "Suwayomi-Server.jar" | head -1)
- if [ -z "$JAVA" ]; then
- echo "ERROR: jre/bin/java.exe not found"
- find suwayomi-extracted -type f | head -50
- exit 1
- fi
- if [ -z "$JAR" ]; then
- echo "ERROR: Suwayomi-Server.jar not found"
- find suwayomi-extracted -type f | head -50
- exit 1
- fi
+ find suwayomi-extracted -path "*/jre/bin/java.exe" | grep -q . \
+ || { echo "ERROR: java.exe not found"; find suwayomi-extracted -type f | head -50; exit 1; }
+ find suwayomi-extracted -name "Suwayomi-Server.jar" | grep -q . \
+ || { echo "ERROR: Suwayomi-Server.jar not found"; find suwayomi-extracted -type f | head -50; exit 1; }
cp -r suwayomi-extracted src-tauri/binaries/suwayomi-bundle
- - name: Validate staging
- shell: bash
- run: |
- find src-tauri/binaries/suwayomi-bundle -path "*/jre/bin/java.exe" \
- | grep -q . || (echo "ERROR: jre/bin/java.exe missing" && exit 1)
- find src-tauri/binaries/suwayomi-bundle -name "Suwayomi-Server.jar" \
- | grep -q . || (echo "ERROR: Suwayomi-Server.jar missing" && exit 1)
- echo "Staging OK"
-
- name: Patch tauri.conf.json for CI
shell: bash
- run: |
- sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
+ run: sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
- - name: Delete existing draft release if present
+ - name: Delete existing draft release
shell: bash
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }
run: |
RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/moku-project/Moku/releases" \
| jq -r '.[] | select(.tag_name == "v${{ github.event.inputs.version }}" and .draft == true) | .id')
if [ -n "$RELEASE_ID" ]; then
- echo "Deleting existing draft release $RELEASE_ID"
curl -s -X DELETE -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/moku-project/Moku/releases/$RELEASE_ID"
curl -s -X DELETE -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/moku-project/Moku/git/refs/tags/v${{ github.event.inputs.version }}"
- echo "Deleted draft release and tag"
- else
- echo "No existing draft release found"
fi
- name: Build Tauri app + create draft release
uses: tauri-apps/tauri-action@v0
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }
with:
tagName: v${{ github.event.inputs.version }}
releaseName: Moku v${{ github.event.inputs.version }}
releaseBody: |
Moku v${{ github.event.inputs.version }}
- **Windows:** Download `Moku_${{ github.event.inputs.version }}_x64-setup.exe`
- **macOS arm64:** Download `moku-macos-arm64-${{ github.event.inputs.version }}.dmg`
- **macOS x64:** Download `moku-macos-x64-${{ github.event.inputs.version }}.dmg`
- **Linux:** Download `moku.flatpak`
+ **Windows:** `Moku_${{ github.event.inputs.version }}_x64-setup.exe`
+ **macOS arm64:** `moku-macos-arm64-${{ github.event.inputs.version }}.dmg`
+ **macOS x64:** `moku-macos-x64-${{ github.event.inputs.version }}.dmg`
+ **Linux:** `moku.flatpak`
releaseDraft: true
prerelease: false
- args: --target x86_64-pc-windows-msvc --config src-tauri/tauri.windows.conf.json
\ No newline at end of file
+ args: --target x86_64-pc-windows-msvc --config src-tauri/tauri.windows.conf.json
diff --git a/build-windows-local.ps1 b/build-windows-local.ps1
new file mode 100644
index 0000000..0b2154f
--- /dev/null
+++ b/build-windows-local.ps1
@@ -0,0 +1,99 @@
+#Requires -Version 7
+param(
+ [switch]$SkipFrontend,
+ [switch]$SkipSuwayomi
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+function Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
+function Need($cmd) {
+ if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
+ Write-Error "Required tool not found: $cmd"
+ }
+}
+
+$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
+Set-Location $Root
+
+Step "Reading nix/versions.nix"
+$nix = Get-Content "$Root\nix\versions.nix" -Raw
+$MOKU_VERSION = if ($nix -match 'moku\s*=\s*"([^"]+)"') { $Matches[1] } else { Write-Error "moku version not found" }
+$SUWA_VERSION = if ($nix -match 'version\s*=\s*"([^"]+)"') { $Matches[1] } else { Write-Error "suwayomi version not found" }
+$SUWA_HASH = if ($nix -match 'windowsHash\s*=\s*"([^"]+)"') { $Matches[1] } else { Write-Error "windowsHash not found" }
+Write-Host " moku=$MOKU_VERSION suwayomi=$SUWA_VERSION"
+
+Need "pnpm"; Need "cargo"; Need "node"
+
+if (-not $SkipFrontend) {
+ Step "pnpm install"
+ pnpm install --frozen-lockfile
+ if ($LASTEXITCODE -ne 0) { Write-Error "pnpm install failed" }
+
+ Step "Frontend build"
+ $env:MOKU_TARGET = "static"
+ pnpm build:static
+ if ($LASTEXITCODE -ne 0) { Write-Error "Frontend build failed" }
+}
+
+$BundleDir = "$Root\src-tauri\binaries\suwayomi-bundle"
+$ZipPath = "$env:TEMP\suwayomi-windows-$SUWA_VERSION.zip"
+$ExtractDir = "$env:TEMP\suwayomi-extracted-$SUWA_VERSION"
+
+if (-not $SkipSuwayomi) {
+ Step "Downloading Suwayomi v$SUWA_VERSION"
+ $ZipUrl = "https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VERSION}/Suwayomi-Server-v${SUWA_VERSION}-windows-x64.zip"
+
+ if (-not (Test-Path $ZipPath)) {
+ Invoke-WebRequest -Uri $ZipUrl -OutFile $ZipPath -UseBasicParsing
+ }
+
+ $actual = (Get-FileHash $ZipPath -Algorithm SHA256).Hash.ToLower()
+ if ($actual -ne $SUWA_HASH.ToLower()) {
+ Write-Error "Hash mismatch`n expected: $SUWA_HASH`n got: $actual"
+ }
+
+ Step "Staging bundle"
+ if (Test-Path $ExtractDir) { Remove-Item $ExtractDir -Recurse -Force }
+ Expand-Archive -Path $ZipPath -DestinationPath $ExtractDir
+
+ $topDirs = @(Get-ChildItem $ExtractDir -Directory)
+ $topFiles = @(Get-ChildItem $ExtractDir -File)
+ $SrcDir = if ($topDirs.Count -eq 1 -and $topFiles.Count -eq 0) { $topDirs[0].FullName } else { $ExtractDir }
+
+ if (Test-Path $BundleDir) { Remove-Item $BundleDir -Recurse -Force }
+ Copy-Item $SrcDir $BundleDir -Recurse
+
+ $java = Get-ChildItem $BundleDir -Recurse -Filter "java.exe" | Where-Object { $_.FullName -match "jre.bin" } | Select-Object -First 1
+ $jar = Get-ChildItem $BundleDir -Recurse -Filter "Suwayomi-Server.jar" | Select-Object -First 1
+ if (-not $java) { Write-Error "java.exe not found in staged bundle" }
+ if (-not $jar) { Write-Error "Suwayomi-Server.jar not found in staged bundle" }
+ Write-Host " java: $($java.FullName)"
+ Write-Host " jar: $($jar.FullName)"
+} elseif (-not (Test-Path $BundleDir)) {
+ Write-Error "Bundle dir missing at $BundleDir — run without -SkipSuwayomi first"
+}
+
+Step "Patching tauri.conf.json"
+$tauriConf = "$Root\src-tauri\tauri.conf.json"
+$original = Get-Content $tauriConf -Raw
+Set-Content $tauriConf ($original -replace '"beforeBuildCommand":\s*"pnpm build"', '"beforeBuildCommand": ""') -NoNewline
+
+Step "Tauri build"
+$env:TAURI_SKIP_DEVSERVER_CHECK = "true"
+pnpm tauri build --target x86_64-pc-windows-msvc --config src-tauri/tauri.windows.conf.json
+$buildExit = $LASTEXITCODE
+
+Set-Content $tauriConf $original -NoNewline
+
+if ($buildExit -ne 0) { Write-Error "Tauri build failed (exit $buildExit)" }
+
+Step "Artifacts"
+$out = "$Root\src-tauri\target\x86_64-pc-windows-msvc\release\bundle"
+$msi = Get-ChildItem "$out\msi" -Filter "*.msi" -ErrorAction SilentlyContinue | Select-Object -First 1
+$exe = Get-ChildItem "$out\nsis" -Filter "*.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
+Write-Host "`nDone — Moku $MOKU_VERSION" -ForegroundColor Green
+if ($msi) { Write-Host " MSI: $($msi.FullName)" -ForegroundColor Yellow }
+if ($exe) { Write-Host " EXE: $($exe.FullName)" -ForegroundColor Yellow }
+if (-not $msi -and -not $exe) { Write-Host " No artifacts found in $out" -ForegroundColor Red }
diff --git a/nix/scripts.nix b/nix/scripts.nix
index 3f366e2..d9804f1 100644
--- a/nix/scripts.nix
+++ b/nix/scripts.nix
@@ -85,17 +85,31 @@ PYEOF
if [[ $# -ge 2 ]]; then
SUWA_VER="$2"
- JAR_URL="https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VER}/Suwayomi-Server-v${SUWA_VER}.jar"
+ BASE="https://github.com/Suwayomi/Suwayomi-Server-preview/releases/download/v${SUWA_VER}"
- SUWA_SHA_HEX=$(curl -fsSL "$JAR_URL" | sha256sum | awk '{print $1}')
- SUWA_SHA_SRI=$(echo "$SUWA_SHA_HEX" | xxd -r -p | base64 -w0 | sed 's/^/sha256-/')
+ echo "Fetching Suwayomi v${SUWA_VER} hashes (5 downloads)..."
- sed -i "s/version = \"[^\"]*\"/version = \"$SUWA_VER\"/" "$VERSIONS"
- sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"$SUWA_SHA_SRI\"|" "$VERSIONS"
+ sha_of() { curl -fsSL "$1" | sha256sum | awk '{print $1}'; }
+ to_sri() { echo "$1" | xxd -r -p | base64 -w0 | sed 's/^/sha256-/'; }
+
+ JAR_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}.jar")
+ WIN_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-windows-x64.zip")
+ LINUX_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-linux-x64.tar.gz")
+ ARM64_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-macOS-arm64.tar.gz")
+ X64_SHA=$(sha_of "${BASE}/Suwayomi-Server-v${SUWA_VER}-macOS-x64.tar.gz")
+
+ JAR_SRI=$(to_sri "$JAR_SHA")
+
+ sed -i "s/version = \"[^\"]*\"/version = \"${SUWA_VER}\"/" "$VERSIONS"
+ sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"${JAR_SRI}\"|" "$VERSIONS"
+ sed -i "s|windowsHash = \"[^\"]*\"|windowsHash = \"${WIN_SHA}\"|" "$VERSIONS"
+ sed -i "s|linuxHash = \"[^\"]*\"|linuxHash = \"${LINUX_SHA}\"|" "$VERSIONS"
+ sed -i "s|macosArm64Hash = \"[^\"]*\"|macosArm64Hash = \"${ARM64_SHA}\"|" "$VERSIONS"
+ sed -i "s|macosX64Hash = \"[^\"]*\"|macosX64Hash = \"${X64_SHA}\"|" "$VERSIONS"
sed -i "s|Suwayomi-Server-preview/releases/download/v[^/]*/|Suwayomi-Server-preview/releases/download/v${SUWA_VER}/|" "$MANIFEST"
sed -i "s|Suwayomi-Server-v[0-9.]*\.jar|Suwayomi-Server-v${SUWA_VER}.jar|g" "$MANIFEST"
- python3 - "$MANIFEST" "$SUWA_SHA_HEX" <<'PYEOF'
+ python3 - "$MANIFEST" "$JAR_SHA" <<'PYEOF'
import re, sys
path, sha = sys.argv[1], sys.argv[2]
text = open(path).read()
@@ -106,6 +120,8 @@ if n == 0:
sys.exit("ERROR: could not find Suwayomi jar sha256 in manifest")
open(path, 'w').write(updated)
PYEOF
+
+ echo "Suwayomi hashes written."
fi
echo "Done — versions.nix, flatpak manifest, and PKGBUILD patched for v$VERSION"
diff --git a/nix/versions.nix b/nix/versions.nix
index c84a271..a3f4abf 100644
--- a/nix/versions.nix
+++ b/nix/versions.nix
@@ -4,11 +4,15 @@
suwayomi = {
version = "2.2.2196";
hash = "sha256-jnJEwmlFZmGodwX3RvDYcnV3Cql2urfGkg5NUT6Xw/Y=";
+ windowsHash = "457ca4a64a57e0d274a87203d25e962103bcb456ee30ada3ea47328a3093329d";
+ linuxHash = "e13d63ceb7e2b15e83d0a78281e8c1c04ac4a833caa73e5a2b68fbaf0cb20c1f";
+ macosArm64Hash = "9e3dbebc7475707e8d11c56a473385c00b09bde0103d013bc1cb3d06c89e5c43";
+ macosX64Hash = "eadee02060b780a5febfb8dada2f89c7bd7db5905cfd20d47eaca02fcde8c9c5";
};
frontend = {
- pnpmHash = "sha256-18twdFhprV9v9hzvqxuVDHD6Tm4zHNDJs7s6l/7ClBo=";
- distHash = "7db288b4b54277aa82b6ec5b21fc31a1e71f8246c50a74777500083b806c1fa5";
+ pnpmHash = "sha256-18twdFhprV9v9hzvqxuVDHD6Tm4zHNDJs7s6l/7ClBo=";
+ distHash = "7db288b4b54277aa82b6ec5b21fc31a1e71f8246c50a74777500083b806c1fa5";
distHashSri = "sha256-fbiiu0tCd6qCtu+SIfw+aR8Yj2bFCnR3dQAIO4BvwfM=";
};
@@ -16,6 +20,6 @@
tauri-plugin-discord-rpc = "sha256-xq0qyK2NrwSAFDhXo0vbvcygRD2/7uqBaLpqfpfxkrc=";
};
- gitCommit = "239960683b6c7f1347e1798b0e179a8a46628728";
+ gitCommit = "239960683b6c7f1347e1798b0e179a8a46628728";
tarballHash = "";
}
diff --git a/src/lib/components/settings/BugReporter.svelte b/src/lib/components/settings/BugReporter.svelte
new file mode 100644
index 0000000..e4d1d95
--- /dev/null
+++ b/src/lib/components/settings/BugReporter.svelte
@@ -0,0 +1,535 @@
+
+
+
What would you like to submit?
+{envBlock}
+