Skip to content

Commit 1e12a16

Browse files
bobokungithub-actions[bot]dependabot[bot]pre-commit-ci[bot]github-advanced-security[bot]
authored
4.5.4 (#910)
# Improvements - Support cross-platform binary builds (Linux/Windows/MacOS) - Adds desktop app installers (Linux/Windows/MacOS) - Container images for latest now pointed to newest version automatically (Fixes #897) - Enable automatic open of webUI in local installs - Add persistence toggling for webUI scheduler # Bug Fixes - Fix schedule.yml not loaded upon restarting Docker container (Fixes #906) - Fix bug where torrents were not being paused after share limits reached (Fixes #901) - Fix(api): prevent path traversal vulnerability in backup restore endpoint (Fixes CWE-22 Security Vulnerability) - Fix scheduler to run interval jobs immediately on startup **Full Changelog**: v4.5.3...v4.5.4 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 9ee3527 commit 1e12a16

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+8536
-568
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414

1515
steps:
1616
- name: Checkout code
17-
uses: actions/checkout@v4
17+
uses: actions/checkout@v5
1818

1919
- name: Set up Python
2020
uses: actions/setup-python@v5
@@ -40,7 +40,7 @@ jobs:
4040
ruff:
4141
runs-on: ubuntu-latest
4242
steps:
43-
- uses: actions/checkout@v4
43+
- uses: actions/checkout@v5
4444
- uses: astral-sh/ruff-action@v3
4545
with:
4646
token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/develop.yml

Lines changed: 357 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,364 @@ on:
44
push:
55
branches: [ develop ]
66

7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: true
10+
711
jobs:
812

13+
build-binaries:
14+
name: Build Standalone Binaries (${{ matrix.os }})
15+
permissions:
16+
contents: read
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
include:
22+
- os: ubuntu-latest
23+
python-version: '3.12'
24+
- os: windows-latest
25+
python-version: '3.12'
26+
- os: 'macos-latest' # for Arm based macs (M1 and above).
27+
python-version: '3.12'
28+
arch: arm64
29+
- os: 'macos-13' # for Intel based macs.
30+
python-version: '3.12'
31+
arch: x86_64
32+
env:
33+
APP_NAME: qbit-manage
34+
ENTRY: qbit_manage.py
35+
steps:
36+
- name: Check Out Repo
37+
uses: actions/checkout@v5
38+
with:
39+
ref: develop
40+
41+
- name: Setup Python
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: ${{ matrix.python-version }}
45+
cache: 'pip'
46+
cache-dependency-path: |
47+
pyproject.toml
48+
uv.lock
49+
50+
- name: Upgrade Pip and install project + PyInstaller
51+
run: |
52+
python -m pip install --upgrade pip wheel
53+
python -m pip install . pyinstaller
54+
55+
- name: Compute add-data separator
56+
id: sep
57+
shell: bash
58+
run: |
59+
if [[ "${{ runner.os }}" == "Windows" ]]; then
60+
echo "SEP=;" >> $GITHUB_OUTPUT
61+
else
62+
echo "SEP=:" >> $GITHUB_OUTPUT
63+
fi
64+
65+
- name: Build (PyInstaller onefile)
66+
shell: bash
67+
run: |
68+
ADD_WEBUI="web-ui${{ steps.sep.outputs.SEP }}web-ui"
69+
ADD_SAMPLE_CFG="config/config.yml.sample${{ steps.sep.outputs.SEP }}config"
70+
ADD_LOGO="icons/qbm_logo.png${{ steps.sep.outputs.SEP }}."
71+
ADD_VERSION="VERSION${{ steps.sep.outputs.SEP }}."
72+
ICON_ARG=""
73+
if [[ "${{ runner.os }}" == "Windows" ]]; then
74+
ICON_ARG=--icon=icons/qbm_logo.ico
75+
elif [[ "${{ runner.os }}" == "macOS" ]]; then
76+
ICON_ARG=--icon=icons/qbm_logo.icns
77+
else
78+
# Linux: optional icon (helps some desktop environments)
79+
if [[ -f icons/qbm_logo.png ]]; then
80+
ICON_ARG=--icon=icons/qbm_logo.png
81+
elif [[ -f icons/qbm_logo.ico ]]; then
82+
ICON_ARG=--icon=icons/qbm_logo.ico
83+
fi
84+
fi
85+
pyinstaller --noconfirm \
86+
--clean \
87+
--onefile \
88+
--name "${APP_NAME}" \
89+
--add-data "$ADD_WEBUI" \
90+
--add-data "$ADD_SAMPLE_CFG" \
91+
--add-data "$ADD_LOGO" \
92+
--add-data "$ADD_VERSION" \
93+
$ICON_ARG \
94+
"${ENTRY}"
95+
96+
- name: Rename output for OS
97+
shell: bash
98+
run: |
99+
mkdir -p out
100+
if [[ "${{ runner.os }}" == "Windows" ]]; then
101+
mv "dist/${APP_NAME}.exe" "out/${APP_NAME}-windows-amd64.exe"
102+
elif [[ "${{ runner.os }}" == "macOS" ]]; then
103+
ARCH=$(uname -m)
104+
if [[ "$ARCH" == "arm64" ]]; then
105+
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-arm64"
106+
else
107+
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-x86_64"
108+
fi
109+
else
110+
mv "dist/${APP_NAME}" "out/${APP_NAME}-linux-amd64"
111+
fi
112+
113+
114+
# Build Tauri desktop shell after binaries are ready
115+
- name: Setup Rust toolchain
116+
uses: dtolnay/rust-toolchain@stable
117+
with:
118+
toolchain: stable
119+
env:
120+
RUSTUP_MAX_RETRIES: 10
121+
timeout-minutes: 10
122+
continue-on-error: true
123+
id: rust-setup
124+
125+
- name: Wait before retry (network recovery)
126+
if: steps.rust-setup.outcome == 'failure'
127+
run: sleep 30
128+
shell: bash
129+
130+
- name: Retry Rust toolchain setup on failure
131+
if: steps.rust-setup.outcome == 'failure'
132+
uses: dtolnay/rust-toolchain@stable
133+
with:
134+
toolchain: stable
135+
env:
136+
RUSTUP_MAX_RETRIES: 10
137+
timeout-minutes: 10
138+
139+
- name: Rust cache
140+
uses: swatinem/rust-cache@v2
141+
with:
142+
workspaces: |
143+
desktop/tauri/src-tauri -> desktop/tauri/src-tauri/target
144+
145+
- name: Install NSIS (Windows)
146+
if: runner.os == 'Windows'
147+
shell: powershell
148+
run: choco install nsis -y
149+
150+
- name: Install Tauri dependencies (Linux)
151+
if: runner.os == 'Linux'
152+
run: |
153+
sudo apt-get update
154+
# Ubuntu 24.04 (noble) uses libwebkit2gtk-4.1-dev (4.0 no longer available)
155+
# Install core deps first
156+
sudo apt-get install -y libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf
157+
# Try new package name, fall back for older runners
158+
sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev
159+
160+
- name: Prepare Tauri server binary
161+
shell: bash
162+
run: |
163+
mkdir -p desktop/tauri/src-tauri/bin
164+
165+
# Copy the actual binary for this platform as a resource
166+
if [[ "${{ runner.os }}" == "Windows" ]]; then
167+
cp "out/${APP_NAME}-windows-amd64.exe" "desktop/tauri/src-tauri/bin/qbit-manage-windows-amd64.exe"
168+
elif [[ "${{ runner.os }}" == "macOS" ]]; then
169+
ARCH=$(uname -m)
170+
if [[ "$ARCH" == "arm64" ]]; then
171+
cp "out/${APP_NAME}-macos-arm64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
172+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
173+
else
174+
cp "out/${APP_NAME}-macos-x86_64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
175+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
176+
fi
177+
else
178+
cp "out/${APP_NAME}-linux-amd64" "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
179+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
180+
fi
181+
182+
- name: Update Tauri version files
183+
working-directory: desktop/tauri/src-tauri
184+
shell: bash
185+
run: |
186+
# Run cargo check to trigger build script and update version files
187+
cargo check
188+
189+
- name: Build Tauri app
190+
working-directory: desktop/tauri/src-tauri
191+
shell: bash
192+
run: |
193+
# Install Tauri 2 CLI (project migrated to Tauri v2 config & deps)
194+
cargo install tauri-cli --version ^2 --locked --force
195+
196+
# Build with explicit bundle targets for this platform
197+
if [[ "${{ runner.os }}" == "Windows" ]]; then
198+
cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis
199+
elif [[ "${{ runner.os }}" == "macOS" ]]; then
200+
cargo tauri build --bundles app,dmg
201+
else
202+
cargo tauri build --bundles deb
203+
fi
204+
205+
- name: Set BUILD_ARCH for artifact naming
206+
shell: bash
207+
run: |
208+
if [[ "${{ runner.os }}" == "macOS" ]]; then
209+
ARCH=$(uname -m)
210+
if [[ "$ARCH" == "arm64" ]]; then
211+
echo "BUILD_ARCH=arm64" >> $GITHUB_ENV
212+
else
213+
echo "BUILD_ARCH=x86_64" >> $GITHUB_ENV
214+
fi
215+
elif [[ "${{ runner.os }}" == "Windows" ]]; then
216+
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
217+
else
218+
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
219+
fi
220+
221+
- name: Upload build outputs (binary + Tauri bundles)
222+
uses: actions/upload-artifact@v4
223+
with:
224+
name: build-outputs-${{ runner.os }}-${{ env.BUILD_ARCH }}
225+
path: |
226+
out/**
227+
desktop/tauri/src-tauri/target/**/release/bundle/**
228+
if-no-files-found: error
229+
retention-days: 7
230+
compression-level: 0
231+
232+
prepare-release-assets:
233+
runs-on: ubuntu-latest
234+
needs: build-binaries
235+
permissions:
236+
actions: write
237+
contents: read
238+
steps:
239+
- name: Download and collect all build outputs
240+
uses: actions/download-artifact@v5
241+
with:
242+
pattern: build-outputs-*
243+
path: collected
244+
merge-multiple: true
245+
246+
- name: Filter and rename files for release
247+
shell: bash
248+
run: |
249+
set -euo pipefail
250+
mkdir -p release-assets
251+
252+
echo "=== Collecting server binaries (standalone executables) ==="
253+
# Copy server binaries with error checking
254+
server_files=(
255+
"qbit-manage-linux-amd64"
256+
"qbit-manage-macos-arm64"
257+
"qbit-manage-macos-x86_64"
258+
"qbit-manage-windows-amd64.exe"
259+
)
260+
261+
for server_file in "${server_files[@]}"; do
262+
if find collected -name "$server_file" -exec cp {} release-assets/ \; -print | grep -q .; then
263+
echo "✓ Found and copied: $server_file"
264+
else
265+
echo "⚠️ Warning: $server_file not found"
266+
fi
267+
done
268+
269+
echo "=== Processing installer files (desktop app packages) ==="
270+
271+
# Linux .deb installer
272+
deb_count=$(find collected -name "*.deb" -exec cp {} release-assets/ \; -print | wc -l)
273+
if [ "$deb_count" -gt 0 ]; then
274+
echo "✓ Found $deb_count .deb installer(s)"
275+
for file in release-assets/*.deb; do
276+
if [ -f "$file" ]; then
277+
basename=$(basename "$file" .deb)
278+
mv "$file" "release-assets/${basename}-desktop-installer.deb"
279+
echo " → Renamed to: ${basename}-desktop-installer.deb"
280+
fi
281+
done
282+
else
283+
echo "⚠️ Warning: No .deb installers found"
284+
fi
285+
286+
# macOS .dmg installers
287+
dmg_count=$(find collected -name "*.dmg" -exec cp {} release-assets/ \; -print | wc -l)
288+
if [ "$dmg_count" -gt 0 ]; then
289+
echo "✓ Found $dmg_count .dmg installer(s)"
290+
for file in release-assets/*.dmg; do
291+
if [ -f "$file" ]; then
292+
basename=$(basename "$file" .dmg)
293+
mv "$file" "release-assets/${basename}-desktop-installer.dmg"
294+
echo " → Renamed to: ${basename}-desktop-installer.dmg"
295+
fi
296+
done
297+
else
298+
echo "⚠️ Warning: No .dmg installers found"
299+
fi
300+
301+
# Windows .exe installer
302+
exe_count=$(find collected -name "*-setup.exe" -exec cp {} release-assets/ \; -print | wc -l)
303+
if [ "$exe_count" -gt 0 ]; then
304+
echo "✓ Found $exe_count .exe installer(s)"
305+
for file in release-assets/*-setup.exe; do
306+
if [ -f "$file" ]; then
307+
basename=$(basename "$file" -setup.exe)
308+
mv "$file" "release-assets/${basename}-desktop-installer-setup.exe"
309+
echo " → Renamed to: ${basename}-desktop-installer-setup.exe"
310+
fi
311+
done
312+
else
313+
echo "⚠️ Warning: No .exe installers found"
314+
fi
315+
316+
echo "=== File processing completed ==="
317+
318+
- name: Display final release assets
319+
run: |
320+
echo "=== Final Release Assets ==="
321+
ls -la release-assets/
322+
echo ""
323+
echo "=== File Count Summary ==="
324+
echo "Server binaries: $(find release-assets -name "qbit-manage-*" -not -name "*desktop*" | wc -l)"
325+
echo "Desktop installers: $(find release-assets -name "*desktop-installer*" | wc -l)"
326+
327+
- name: Upload final release assets
328+
uses: actions/upload-artifact@v4
329+
with:
330+
name: qbit-manage-release-assets
331+
path: release-assets/*
332+
if-no-files-found: error
333+
compression-level: 6
334+
335+
- name: Clean up temporary build artifacts
336+
uses: actions/github-script@v7
337+
with:
338+
script: |
339+
// Get all artifacts from this workflow run
340+
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
341+
owner: context.repo.owner,
342+
repo: context.repo.repo,
343+
run_id: context.runId,
344+
});
345+
346+
// Delete temporary build artifacts, keep only the final release assets
347+
for (const artifact of artifacts.data.artifacts) {
348+
if (artifact.name.startsWith('build-outputs-')) {
349+
console.log(`Deleting temporary artifact: ${artifact.name}`);
350+
try {
351+
await github.rest.actions.deleteArtifact({
352+
owner: context.repo.owner,
353+
repo: context.repo.repo,
354+
artifact_id: artifact.id,
355+
});
356+
console.log(`✓ Successfully deleted: ${artifact.name}`);
357+
} catch (error) {
358+
console.log(`⚠️ Failed to delete ${artifact.name}: ${error.message}`);
359+
}
360+
} else {
361+
console.log(`✓ Keeping final artifact: ${artifact.name}`);
362+
}
363+
}
364+
9365
docker-develop:
10366
runs-on: ubuntu-latest
11367

@@ -17,7 +373,7 @@ jobs:
17373
OWNER: '${{ github.repository_owner }}'
18374

19375
- name: Check Out Repo
20-
uses: actions/checkout@v4
376+
uses: actions/checkout@v5
21377
with:
22378
ref: develop
23379

0 commit comments

Comments
 (0)