Skip to content

Commit 23693e5

Browse files
authored
New: Add Linux ARM64 standalone binary and desktop builds (#1092)
## Summary - Adds `ubuntu-24.04-arm` to the CI build matrix (both release and develop workflows) - Produces `qbit-manage-linux-arm64` standalone binary and ARM64 `.deb` desktop installer - Adds explicit `arch` field to all matrix entries for consistency - Simplifies `BUILD_ARCH` detection to use `matrix.arch` directly instead of runtime detection ## What this enables - Native Linux ARM64 standalone binary (no Docker required) - ARM64 `.deb` desktop installer with the Tauri GUI - Useful for Raspberry Pi and other ARM64 Linux devices ## Changes - `.github/workflows/version.yml` — ARM64 matrix entry, arch-aware rename/copy/artifact steps - `.github/workflows/develop.yml` — identical changes for develop builds ## Notes - Docker images already support `linux/arm64` via QEMU — this change adds native binary/desktop builds - `ubuntu-24.04-arm` runners are free for public repos ([GitHub announcement](https://github.com/orgs/community/discussions/148648)) - The wiki Installation page should be updated to document the new ARM64 assets (will be done separately) ## Test plan - [ ] Verify develop workflow triggers and ARM64 job runs successfully - [ ] Confirm `qbit-manage-linux-arm64` binary appears in artifacts - [ ] Confirm ARM64 `.deb` desktop installer appears in artifacts - [ ] Verify existing amd64/macOS/Windows builds are unaffected
1 parent 7d23603 commit 23693e5

File tree

7 files changed

+239
-76
lines changed

7 files changed

+239
-76
lines changed

.github/workflows/develop.yml

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ jobs:
2121
include:
2222
- os: ubuntu-latest
2323
python-version: "3.12"
24+
arch: amd64
25+
- os: ubuntu-24.04-arm # for Arm based Linux (Raspberry Pi, etc.)
26+
python-version: "3.12"
27+
arch: arm64
2428
- os: windows-latest
2529
python-version: "3.12"
30+
arch: amd64
2631
- os: "macos-latest" # for Arm based macs (M1 and above).
2732
python-version: "3.12"
2833
arch: arm64
@@ -100,16 +105,11 @@ jobs:
100105
run: |
101106
mkdir -p out
102107
if [[ "${{ runner.os }}" == "Windows" ]]; then
103-
mv "dist/${APP_NAME}.exe" "out/${APP_NAME}-windows-amd64.exe"
108+
mv "dist/${APP_NAME}.exe" "out/${APP_NAME}-windows-${{ matrix.arch }}.exe"
104109
elif [[ "${{ runner.os }}" == "macOS" ]]; then
105-
ARCH=$(uname -m)
106-
if [[ "$ARCH" == "arm64" ]]; then
107-
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-arm64"
108-
else
109-
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-x86_64"
110-
fi
110+
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-${{ matrix.arch }}"
111111
else
112-
mv "dist/${APP_NAME}" "out/${APP_NAME}-linux-amd64"
112+
mv "dist/${APP_NAME}" "out/${APP_NAME}-linux-${{ matrix.arch }}"
113113
fi
114114
115115
# Build Tauri desktop shell after binaries are ready
@@ -165,19 +165,13 @@ jobs:
165165
166166
# Copy the actual binary for this platform as a resource
167167
if [[ "${{ runner.os }}" == "Windows" ]]; then
168-
cp "out/${APP_NAME}-windows-amd64.exe" "desktop/tauri/src-tauri/bin/qbit-manage-windows-amd64.exe"
168+
cp "out/${APP_NAME}-windows-${{ matrix.arch }}.exe" "desktop/tauri/src-tauri/bin/qbit-manage-windows-${{ matrix.arch }}.exe"
169169
elif [[ "${{ runner.os }}" == "macOS" ]]; then
170-
ARCH=$(uname -m)
171-
if [[ "$ARCH" == "arm64" ]]; then
172-
cp "out/${APP_NAME}-macos-arm64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
173-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
174-
else
175-
cp "out/${APP_NAME}-macos-x86_64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
176-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
177-
fi
170+
cp "out/${APP_NAME}-macos-${{ matrix.arch }}" "desktop/tauri/src-tauri/bin/qbit-manage-macos-${{ matrix.arch }}"
171+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-${{ matrix.arch }}"
178172
else
179-
cp "out/${APP_NAME}-linux-amd64" "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
180-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
173+
cp "out/${APP_NAME}-linux-${{ matrix.arch }}" "desktop/tauri/src-tauri/bin/qbit-manage-linux-${{ matrix.arch }}"
174+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-linux-${{ matrix.arch }}"
181175
fi
182176
183177
- name: Update Tauri version files
@@ -264,18 +258,7 @@ jobs:
264258
- name: Set BUILD_ARCH for artifact naming
265259
shell: bash
266260
run: |
267-
if [[ "${{ runner.os }}" == "macOS" ]]; then
268-
ARCH=$(uname -m)
269-
if [[ "$ARCH" == "arm64" ]]; then
270-
echo "BUILD_ARCH=arm64" >> $GITHUB_ENV
271-
else
272-
echo "BUILD_ARCH=x86_64" >> $GITHUB_ENV
273-
fi
274-
elif [[ "${{ runner.os }}" == "Windows" ]]; then
275-
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
276-
else
277-
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
278-
fi
261+
echo "BUILD_ARCH=${{ matrix.arch }}" >> $GITHUB_ENV
279262
280263
- name: Upload build outputs (binary + Tauri bundles)
281264
uses: actions/upload-artifact@v7
@@ -312,6 +295,7 @@ jobs:
312295
# Copy server binaries with error checking
313296
server_files=(
314297
"qbit-manage-linux-amd64"
298+
"qbit-manage-linux-arm64"
315299
"qbit-manage-macos-arm64"
316300
"qbit-manage-macos-x86_64"
317301
"qbit-manage-windows-amd64.exe"

.github/workflows/version.yml

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ jobs:
2121
include:
2222
- os: ubuntu-latest
2323
python-version: "3.12"
24+
arch: amd64
25+
- os: ubuntu-24.04-arm # for Arm based Linux (Raspberry Pi, etc.)
26+
python-version: "3.12"
27+
arch: arm64
2428
- os: windows-latest
2529
python-version: "3.12"
30+
arch: amd64
2631
- os: "macos-latest" # for Arm based macs (M1 and above).
2732
python-version: "3.12"
2833
arch: arm64
@@ -98,16 +103,11 @@ jobs:
98103
run: |
99104
mkdir -p out
100105
if [[ "${{ runner.os }}" == "Windows" ]]; then
101-
mv "dist/${APP_NAME}.exe" "out/${APP_NAME}-windows-amd64.exe"
106+
mv "dist/${APP_NAME}.exe" "out/${APP_NAME}-windows-${{ matrix.arch }}.exe"
102107
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
108+
mv "dist/${APP_NAME}" "out/${APP_NAME}-macos-${{ matrix.arch }}"
109109
else
110-
mv "dist/${APP_NAME}" "out/${APP_NAME}-linux-amd64"
110+
mv "dist/${APP_NAME}" "out/${APP_NAME}-linux-${{ matrix.arch }}"
111111
fi
112112
113113
# Build Tauri desktop shell after binaries are ready
@@ -163,19 +163,13 @@ jobs:
163163
164164
# Copy the actual binary for this platform as a resource
165165
if [[ "${{ runner.os }}" == "Windows" ]]; then
166-
cp "out/${APP_NAME}-windows-amd64.exe" "desktop/tauri/src-tauri/bin/qbit-manage-windows-amd64.exe"
166+
cp "out/${APP_NAME}-windows-${{ matrix.arch }}.exe" "desktop/tauri/src-tauri/bin/qbit-manage-windows-${{ matrix.arch }}.exe"
167167
elif [[ "${{ runner.os }}" == "macOS" ]]; then
168-
ARCH=$(uname -m)
169-
if [[ "$ARCH" == "arm64" ]]; then
170-
cp "out/${APP_NAME}-macos-arm64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
171-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64"
172-
else
173-
cp "out/${APP_NAME}-macos-x86_64" "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
174-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64"
175-
fi
168+
cp "out/${APP_NAME}-macos-${{ matrix.arch }}" "desktop/tauri/src-tauri/bin/qbit-manage-macos-${{ matrix.arch }}"
169+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-macos-${{ matrix.arch }}"
176170
else
177-
cp "out/${APP_NAME}-linux-amd64" "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
178-
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64"
171+
cp "out/${APP_NAME}-linux-${{ matrix.arch }}" "desktop/tauri/src-tauri/bin/qbit-manage-linux-${{ matrix.arch }}"
172+
chmod +x "desktop/tauri/src-tauri/bin/qbit-manage-linux-${{ matrix.arch }}"
179173
fi
180174
181175
- name: Update Tauri version files
@@ -262,18 +256,7 @@ jobs:
262256
- name: Set BUILD_ARCH for artifact naming
263257
shell: bash
264258
run: |
265-
if [[ "${{ runner.os }}" == "macOS" ]]; then
266-
ARCH=$(uname -m)
267-
if [[ "$ARCH" == "arm64" ]]; then
268-
echo "BUILD_ARCH=arm64" >> $GITHUB_ENV
269-
else
270-
echo "BUILD_ARCH=x86_64" >> $GITHUB_ENV
271-
fi
272-
elif [[ "${{ runner.os }}" == "Windows" ]]; then
273-
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
274-
else
275-
echo "BUILD_ARCH=amd64" >> $GITHUB_ENV
276-
fi
259+
echo "BUILD_ARCH=${{ matrix.arch }}" >> $GITHUB_ENV
277260
278261
- name: Upload build outputs (binary + Tauri bundles)
279262
uses: actions/upload-artifact@v7
@@ -310,6 +293,7 @@ jobs:
310293
# Copy server binaries with error checking
311294
server_files=(
312295
"qbit-manage-linux-amd64"
296+
"qbit-manage-linux-arm64"
313297
"qbit-manage-macos-arm64"
314298
"qbit-manage-macos-x86_64"
315299
"qbit-manage-windows-amd64.exe"

desktop/tauri/src-tauri/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ fn get_binary_names() -> Vec<&'static str> {
365365
vec![
366366
"qbit-manage",
367367
"qbit-manage-linux-amd64",
368+
"qbit-manage-linux-arm64",
368369
"qbit-manage-macos-x86_64",
369370
"qbit-manage-macos-arm64"
370371
]

docs/Contributing.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Contributing & Building
2+
3+
Pull requests are welcome! Please submit them to the [develop branch](https://github.com/StuffAnThings/qbit_manage/tree/develop).
4+
5+
## Prerequisites
6+
7+
- **Python 3.9+**
8+
- **Git**
9+
- **[uv](https://docs.astral.sh/uv/)** (Python package manager)
10+
- **[Rust](https://rustup.rs/)** (only for desktop app builds)
11+
12+
## Development Setup
13+
14+
```bash
15+
# Clone the repository
16+
git clone https://github.com/StuffAnThings/qbit_manage.git
17+
cd qbit_manage
18+
git checkout develop
19+
20+
# Create virtual environment and install all dependencies (including dev tools)
21+
make venv
22+
23+
# Activate the virtual environment
24+
source .venv/bin/activate
25+
26+
# Install pre-commit hooks
27+
make install-hooks
28+
```
29+
30+
The `make venv` target handles everything: installs uv if needed, creates a `.venv`, installs the project in editable mode, and adds dev dependencies (pre-commit, ruff).
31+
32+
## Common Make Targets
33+
34+
| Target | Description |
35+
|--------|-------------|
36+
| `make venv` | Create virtual environment and install all dependencies |
37+
| `make install-hooks` | Install pre-commit hooks into your local repo |
38+
| `make test` | Run the test suite |
39+
| `make lint` | Run ruff linter with auto-fix |
40+
| `make format` | Run ruff code formatter |
41+
| `make pre-commit` | Run all pre-commit hooks on all files |
42+
| `make build` | Build Python package for distribution |
43+
| `make install` | Install qbit-manage as a `uv tool` (system-wide CLI) |
44+
| `make clean` | Remove all generated files (venv, dist, build, cache) |
45+
| `make help` | Show all available targets (including release/publishing targets) |
46+
47+
## Code Style
48+
49+
The project uses [Ruff](https://docs.astral.sh/ruff/) for both linting and formatting:
50+
51+
- **Line length**: 130 characters
52+
- **Import style**: Single-line imports (enforced by isort rules)
53+
- **Pre-commit hooks** run automatically on commit and enforce:
54+
- Trailing whitespace removal
55+
- End-of-file fixer
56+
- JSON/YAML validation
57+
- Ruff linting and formatting
58+
- Automatic develop version bumping
59+
60+
## Building
61+
62+
### Standalone Binary (PyInstaller)
63+
64+
The standalone binary bundles Python, all dependencies, the web UI, and docs into a single executable.
65+
66+
```bash
67+
# Activate the virtual environment
68+
source .venv/bin/activate
69+
70+
# Install PyInstaller
71+
pip install pyinstaller
72+
73+
# Build the binary
74+
pyinstaller --noconfirm --clean --onefile \
75+
--name qbit-manage \
76+
--add-data "web-ui:web-ui" \
77+
--add-data "config/config.yml.sample:config" \
78+
--add-data "icons/qbm_logo.png:." \
79+
--add-data "VERSION:." \
80+
--add-data "docs:docs" \
81+
qbit_manage.py
82+
83+
# Binary will be in dist/qbit-manage
84+
./dist/qbit-manage --help
85+
```
86+
87+
> **Note:** PyInstaller produces a binary for the architecture of the machine you build on. To get an ARM64 binary, build on an ARM64 machine.
88+
89+
> **Note:** On Windows, replace `:` with `;` in the `--add-data` arguments (e.g., `--add-data "web-ui;web-ui"`).
90+
91+
### Desktop App (Tauri)
92+
93+
The desktop app wraps the standalone binary in a [Tauri v2](https://v2.tauri.app/) shell with a native window, system tray, and the web UI as the frontend.
94+
95+
#### Install Tauri Build Dependencies
96+
97+
**Linux (Debian/Ubuntu):**
98+
```bash
99+
sudo make tauri-deps
100+
```
101+
102+
This installs the required system packages (`build-essential`, `libgtk-3-dev`, `libwebkit2gtk-4.1-dev`, `libayatana-appindicator3-dev`, `librsvg2-dev`, `patchelf`, and more). Requires root for `apt-get`.
103+
104+
**macOS:** Xcode Command Line Tools are sufficient.
105+
106+
**Windows:** Install [NSIS](https://nsis.sourceforge.io/) (e.g., `choco install nsis`).
107+
108+
#### Build Steps
109+
110+
1. **Build the standalone binary first** (see above) and copy it into the Tauri sidecar directory with the platform-specific name:
111+
```bash
112+
mkdir -p desktop/tauri/src-tauri/bin
113+
114+
# Copy only YOUR platform's binary (pick one):
115+
cp dist/qbit-manage desktop/tauri/src-tauri/bin/qbit-manage-linux-amd64 # x86_64 Linux
116+
# cp dist/qbit-manage desktop/tauri/src-tauri/bin/qbit-manage-linux-arm64 # ARM64 Linux
117+
# cp dist/qbit-manage desktop/tauri/src-tauri/bin/qbit-manage-macos-arm64 # Apple Silicon
118+
# cp dist/qbit-manage desktop/tauri/src-tauri/bin/qbit-manage-macos-x86_64 # Intel Mac
119+
120+
chmod +x desktop/tauri/src-tauri/bin/qbit-manage-*
121+
```
122+
123+
2. **Install the Tauri CLI and build:**
124+
```bash
125+
cd desktop/tauri/src-tauri
126+
cargo install tauri-cli --version ^2 --locked
127+
cargo tauri build --bundles deb # Linux: produces .deb in target/release/bundle/deb/
128+
cargo tauri build --bundles dmg # macOS: produces .dmg
129+
cargo tauri build --bundles nsis # Windows: produces installer .exe
130+
```
131+
132+
The build script (`build.rs`) automatically reads the `VERSION` file and updates `Cargo.toml` and `tauri.conf.json` to keep versions in sync.
133+
134+
### Docker Image
135+
136+
```bash
137+
docker build -t qbit-manage .
138+
```
139+
140+
The Dockerfile is a multi-stage Alpine build. For multi-architecture builds (amd64, arm64, arm/v7), the CI uses Docker Buildx with QEMU.
141+
142+
## Project Structure
143+
144+
```
145+
qbit_manage/
146+
├── qbit_manage.py # Main entry point
147+
├── modules/ # Core application modules
148+
├── web-ui/ # Web UI frontend (HTML/CSS/JS)
149+
│ ├── index.html
150+
│ ├── js/
151+
│ └── css/
152+
├── desktop/tauri/ # Tauri desktop app shell
153+
│ └── src-tauri/
154+
│ ├── src/ # Rust source
155+
│ ├── bin/ # Sidecar binaries (gitignored, populated at build time)
156+
│ ├── Cargo.toml
157+
│ ├── build.rs # Version sync script
158+
│ └── tauri.conf.json # Tauri configuration
159+
├── config/ # Sample configuration files
160+
├── docs/ # Wiki documentation (synced to GitHub wiki)
161+
├── icons/ # Application icons
162+
├── scripts/ # Standalone helper scripts
163+
├── Makefile # Development automation
164+
├── Dockerfile # Docker build
165+
├── pyproject.toml # Python project configuration
166+
└── VERSION # Single source of truth for version
167+
```
168+
169+
## Submitting Changes
170+
171+
1. Fork the repository and create a branch from `develop`
172+
2. Make your changes and ensure `make pre-commit` passes
173+
3. Test your changes locally
174+
4. Submit a pull request to the `develop` branch
175+
5. Describe what your PR does and why
176+
177+
## Support
178+
179+
- **Questions**: Join the [Notifiarr Discord](https://discord.com/invite/AURf8Yz) and post in the `qbit-manage` channel
180+
- **Bugs/Enhancements**: Open an [Issue](https://github.com/StuffAnThings/qbit_manage/issues/new)
181+
- **Config Questions**: Start a [Discussion](https://github.com/StuffAnThings/qbit_manage/discussions/new)

docs/Home.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This wiki should tell you everything you need to know about the script to get it
2525
* If you have any questions or require support please join the [Notifiarr Discord](https://discord.com/invite/AURf8Yz) and post your question under the `qbit-manage` channel.
2626
* If you're getting an Error or have an Enhancement post in the [Issues](https://github.com/StuffAnThings/qbit_manage/issues/new).
2727
* If you have a configuration question post in the [Discussions](https://github.com/StuffAnThings/qbit_manage/discussions/new).
28-
* Pull Request are welcome but please submit them to the [develop branch](https://github.com/StuffAnThings/qbit_manage/tree/develop).
28+
* Pull requests are welcome! See the [Contributing & Building](Contributing) guide for setup instructions. Please submit PRs to the [develop branch](https://github.com/StuffAnThings/qbit_manage/tree/develop).
2929

3030
## Table of Contents
3131

@@ -56,6 +56,7 @@ This wiki should tell you everything you need to know about the script to get it
5656
- [Commands](Commands)
5757
- [Web API](Web-API)
5858
- [Web UI](Web-UI)
59+
- [Contributing & Building](Contributing)
5960
- Extras
6061
- [Standalone Scripts](Standalone-Scripts)
6162
- [V4 Migration Guide](v4-Migration-Guide)

0 commit comments

Comments
 (0)