Krolyxon
Commit f7d0d70

Branches

Refs →

budctlf7d0d70

Release 0.1.1: builds for x86_64 and aarch64 Linux, and an install script

- Version 0.1.1 for the whole workspace, and CHANGELOG.md with its notes.
- .github/workflows/release.yml: a vX.Y.Z tag checks the tag against
  Cargo.toml, runs the tests, builds on Ubuntu 22.04 (x86_64 and arm runners,
  so the binaries need glibc 2.35+; Wayland is loaded at runtime) and publishes
  the tarballs, their SHA-256 files and install.sh as a GitHub release.
- .github/workflows/ci.yml: fmt, clippy and the tests on every push to master.
- install.sh: installs budctl, budctl-gui and the launcher entry into ~/.local
  (or --prefix, with sudo only when needed), from a checked download
  (curl ... | sh), from the unpacked tarball it is in, or from a checkout with
  --source. --uninstall leaves the user's config and log alone.
- Release profile: stripped, thin LTO.
Author
krolyxon
Date
2026-10-28 10:43 +0530
Parent
7b28e7d
Commit
f7d0d70d6cfa170873edfbc394295794240b37e1

+317 −8

8 files changed

  1. A .github/workflows/ci.yml +22 −0
  2. A .github/workflows/release.yml +66 −0
  3. A CHANGELOG.md +19 −0
  4. M CLAUDE.md +7 −0
  5. M Cargo.lock +3 −3
  6. M Cargo.toml +7 −1
  7. M README.md +14 −4
  8. A install.sh +179 −0
A .github/workflows/ci.yml View
@@ -0,0 +1,22 @@
1+name: CI
2+
3+# Formatting, lints and tests on every push to master and every pull request.
4+
5+on:
6+ push:
7+ branches: [master]
8+ pull_request:
9+
10+jobs:
11+ check:
12+ runs-on: ubuntu-22.04
13+ steps:
14+ - uses: actions/checkout@v6
15+ - uses: dtolnay/rust-toolchain@stable
16+ with:
17+ components: clippy, rustfmt
18+ - run: sudo apt-get update && sudo apt-get install -y pkg-config libwayland-dev libxkbcommon-dev
19+ - run: cargo fmt --all -- --check
20+ - run: cargo clippy --workspace --all-targets --locked -- -D warnings
21+ - run: cargo test --workspace --locked
22+ - run: sh -n install.sh
A .github/workflows/release.yml View
@@ -0,0 +1,66 @@
1+name: Release
2+
3+# A tag like v0.1.1 builds budctl and budctl-gui for x86_64 and aarch64 Linux and publishes them,
4+# with install.sh and checksums, as a GitHub release. The builds run on Ubuntu 22.04, so they need
5+# glibc 2.35 or newer and nothing else at build time: Wayland and xkbcommon are loaded at runtime.
6+
7+on:
8+ push:
9+ tags: ["v[0-9]+.[0-9]+.[0-9]+"]
10+
11+permissions:
12+ contents: write
13+
14+jobs:
15+ build:
16+ strategy:
17+ matrix:
18+ include:
19+ - target: x86_64-unknown-linux-gnu
20+ runner: ubuntu-22.04
21+ - target: aarch64-unknown-linux-gnu
22+ runner: ubuntu-22.04-arm
23+ runs-on: ${{ matrix.runner }}
24+ steps:
25+ - uses: actions/checkout@v6
26+ - name: The tag matches the version in Cargo.toml
27+ run: |
28+ version=$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)
29+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
30+ echo "tag $GITHUB_REF_NAME, but Cargo.toml says $version"
31+ exit 1
32+ fi
33+ - uses: dtolnay/rust-toolchain@stable
34+ - run: sudo apt-get update && sudo apt-get install -y pkg-config libwayland-dev libxkbcommon-dev
35+ - run: cargo test --workspace --locked
36+ - run: cargo build --release --locked -p budctl -p budctl-gui
37+ - name: Package
38+ run: |
39+ dir=budctl-${{ matrix.target }}
40+ mkdir "$dir"
41+ cp target/release/budctl target/release/budctl-gui contrib/budctl.desktop install.sh README.md LICENSE "$dir/"
42+ tar -czf "$dir.tar.gz" "$dir"
43+ sha256sum "$dir.tar.gz" > "$dir.tar.gz.sha256"
44+ - uses: actions/upload-artifact@v7
45+ with:
46+ name: ${{ matrix.target }}
47+ path: budctl-${{ matrix.target }}.tar.gz*
48+ if-no-files-found: error
49+
50+ publish:
51+ needs: build
52+ runs-on: ubuntu-latest
53+ steps:
54+ - uses: actions/checkout@v6
55+ - uses: actions/download-artifact@v8
56+ with:
57+ path: dist
58+ merge-multiple: true
59+ - name: Release notes from CHANGELOG.md
60+ run: |
61+ awk -v v="${GITHUB_REF_NAME#v}" '/^## / { p = ($2 == v); next } p' CHANGELOG.md > notes.md
62+ test -s notes.md || { echo "CHANGELOG.md has no section for ${GITHUB_REF_NAME#v}"; exit 1; }
63+ - name: Publish
64+ env:
65+ GH_TOKEN: ${{ github.token }}
66+ run: gh release create "$GITHUB_REF_NAME" --title "budctl $GITHUB_REF_NAME" --notes-file notes.md dist/* install.sh
A CHANGELOG.md View
@@ -0,0 +1,19 @@
1+# Changelog
2+
3+## 0.1.1 (2026-10-28)
4+
5+The first release, with builds for x86_64 and aarch64 Linux and an install script.
6+
7+Supported buds: realme Buds T310.
8+
9+- `budctl`, the command line: `list`, `status`, noise control and its levels, game mode, EQ
10+ presets and custom EQ presets (create, edit, rename, delete), switches (bass boost, 3D sound,
11+ wind, dual connection), bass level, find (ring the buds), touch controls, the hold cycle, and
12+ the devices dual connection keeps. `--json` for scripts and status bars, `--debug` for hex
13+ dumps.
14+- `budctl-gui`, the desktop app (Wayland): the same features on five pages, mouse first with
15+ vim-like keys on top, colors from a matugen palette.
16+- `~/.config/budctl/config.json`: a default device, and the desktop app's idle time and start
17+ page. Created with the defaults when missing.
18+- The desktop app writes its built-in colors to `~/.config/budctl/theme.json` when there is no
19+ such file, and logs errors and session events to `~/.local/state/budctl/gui.log`.
M CLAUDE.md View
@@ -137,6 +137,13 @@ Exit codes:
137137 - Keep the encode/decode logic pure and covered by tests, so most changes can be verified without hardware.
138138 - Before adding a feature, check the spec says the T310 supports it. The protocol family covers many models, and not every command applies to this one. The T310's command bitmap (spec §5) is the authority: never send a command whose bit is off.
139139
140+## Releases
141+
142+- One version for the whole workspace (`Cargo.toml`), with a section per release in `CHANGELOG.md`.
143+- To release: bump the version, add its `CHANGELOG.md` section, commit, then tag `vX.Y.Z` and push the tag. `.github/workflows/release.yml` checks the tag matches `Cargo.toml`, runs the tests, builds x86_64 and aarch64 on Ubuntu 22.04 (glibc builds; Wayland is loaded at runtime), and publishes the tarballs, their `.sha256` files and `install.sh`, with that changelog section as the notes.
144+- `install.sh` installs into `~/.local` by default: from a release download (`curl … | sh`), from the unpacked tarball it sits in, or from a checkout with `--source`. `--uninstall` removes the binaries and the launcher entry, never the user's config or log.
145+- `.github/workflows/ci.yml` runs fmt, clippy and the tests on every push to master.
146+
140147 ## License
141148
142149 GPL-3.0-or-later (`LICENSE`). Rule 2 forbids copying other projects' code.
M Cargo.lock View
@@ -327,7 +327,7 @@ dependencies = [
327327
328328 [[package]]
329329 name = "budctl"
330−version = "0.1.0"
330+version = "0.1.1"
331331 dependencies = [
332332 "budctl-core",
333333 "clap",
@@ -336,7 +336,7 @@ dependencies = [
336336
337337 [[package]]
338338 name = "budctl-core"
339−version = "0.1.0"
339+version = "0.1.1"
340340 dependencies = [
341341 "libc",
342342 "serde",
@@ -346,7 +346,7 @@ dependencies = [
346346
347347 [[package]]
348348 name = "budctl-gui"
349−version = "0.1.0"
349+version = "0.1.1"
350350 dependencies = [
351351 "budctl-core",
352352 "iced",
M Cargo.toml View
@@ -3,7 +3,7 @@ resolver = "3"
33 members = ["crates/budctl-core", "crates/budctl-cli", "crates/budctl-gui"]
44
55 [workspace.package]
6−version = "0.1.0"
6+version = "0.1.1"
77 edition = "2024"
88 rust-version = "1.88"
99 license = "GPL-3.0-or-later"
@@ -21,3 +21,9 @@ panic = "deny"
2121 indexing_slicing = "deny"
2222 cast_possible_truncation = "deny"
2323 undocumented_unsafe_blocks = "deny"
24+
25+# Release builds: the binaries the install script ships. No debug symbols, and whole-crate
26+# optimization across the dependency tree, for smaller and faster binaries.
27+[profile.release]
28+strip = true
29+lto = "thin"
M README.md View
@@ -15,14 +15,24 @@ touch controls, battery and more. There is a command-line tool, `budctl`, and a
1515
1616 ## Install
1717
18−You need Rust 1.88 or newer, BlueZ, and for the desktop app a Wayland desktop.
18+You need Linux with BlueZ, and for the desktop app a Wayland desktop. Release builds are for
19+x86_64 and aarch64 (glibc 2.35 or newer).
1920
2021 ```sh
21−cargo install --path crates/budctl-cli # budctl
22−cargo install --path crates/budctl-gui # budctl-gui
23−cp contrib/budctl.desktop ~/.local/share/applications/
22+curl -fsSL https://github.com/krolyxon/budctl/releases/latest/download/install.sh | sh
2423 ```
2524
25+This puts `budctl` and `budctl-gui` in `~/.local/bin`, and a launcher entry in
26+`~/.local/share/applications`. The download is checked against its SHA-256 checksum. Options:
27+
28+```sh
29+sh install.sh --version 0.1.1 # a given release instead of the latest
30+sh install.sh --prefix /usr/local # elsewhere (uses sudo when it must)
31+sh install.sh --uninstall # remove them again; your config and log stay
32+```
33+
34+To build from source instead (Rust 1.88 or newer), run `./install.sh --source` in a checkout.
35+
2636 Pair the buds and connect them in your Bluetooth settings first.
2737
2838 ## Command line
A install.sh View
@@ -0,0 +1,179 @@
1+#!/bin/sh
2+# Install budctl and budctl-gui on Linux, or remove them.
3+#
4+# curl -fsSL https://github.com/krolyxon/budctl/releases/latest/download/install.sh | sh
5+# ./install.sh in an unpacked release: installs the binaries next to it
6+# ./install.sh --source in a checkout: builds with cargo, then installs
7+# ./install.sh --uninstall
8+#
9+# Installs into ~/.local by default (bin/ and share/applications/), so no root is needed.
10+
11+set -eu
12+
13+REPO=krolyxon/budctl
14+PREFIX=$HOME/.local
15+VERSION=latest
16+ACTION=install
17+FROM=auto
18+
19+usage() {
20+ cat <<'EOF'
21+Install budctl (command line) and budctl-gui (desktop app), or remove them.
22+
23+Usage:
24+ install.sh [--prefix DIR] [--version X.Y.Z] [--source]
25+ install.sh --uninstall [--prefix DIR]
26+
27+Options:
28+ --prefix DIR Install into DIR/bin and DIR/share/applications (default: ~/.local)
29+ --version X.Y.Z Download this release instead of the latest
30+ --source Build from this checkout with cargo instead of downloading
31+ --uninstall Remove what install.sh installed. Your config (~/.config/budctl)
32+ and log (~/.local/state/budctl) are left alone.
33+ -h, --help Show this help
34+EOF
35+}
36+
37+fail() {
38+ printf 'install.sh: %s\n' "$1" >&2
39+ exit 1
40+}
41+
42+say() {
43+ printf '%s\n' "$1"
44+}
45+
46+while [ "$#" -gt 0 ]; do
47+ case "$1" in
48+ --prefix)
49+ [ "$#" -ge 2 ] && [ -n "$2" ] || fail "--prefix needs a directory"
50+ PREFIX=$2
51+ shift 2
52+ ;;
53+ --version)
54+ [ "$#" -ge 2 ] && [ -n "$2" ] || fail "--version needs a version, like 0.1.1"
55+ VERSION=${2#v}
56+ shift 2
57+ ;;
58+ --source)
59+ FROM=source
60+ shift
61+ ;;
62+ --uninstall)
63+ ACTION=uninstall
64+ shift
65+ ;;
66+ -h | --help)
67+ usage
68+ exit 0
69+ ;;
70+ *)
71+ fail "unknown option: $1 (see --help)"
72+ ;;
73+ esac
74+done
75+
76+[ "$(uname -s)" = Linux ] || fail "budctl runs on Linux only"
77+
78+BIN_DIR=$PREFIX/bin
79+APP_DIR=$PREFIX/share/applications
80+
81+# Use sudo only when the prefix cannot be written as this user (such as /usr/local).
82+SUDO=
83+writable_dir=$PREFIX
84+while [ ! -d "$writable_dir" ]; do
85+ writable_dir=$(dirname "$writable_dir")
86+done
87+if [ ! -w "$writable_dir" ] && [ "$(id -u)" -ne 0 ]; then
88+ command -v sudo >/dev/null 2>&1 || fail "$PREFIX is not writable and sudo was not found"
89+ SUDO=sudo
90+fi
91+
92+if [ "$ACTION" = uninstall ]; then
93+ for file in "$BIN_DIR/budctl" "$BIN_DIR/budctl-gui" "$APP_DIR/budctl.desktop"; do
94+ if [ -e "$file" ]; then
95+ $SUDO rm -f "$file"
96+ say "Removed $file"
97+ fi
98+ done
99+ say "Your config (~/.config/budctl) and log (~/.local/state/budctl) were left alone."
100+ exit 0
101+fi
102+
103+WORK=$(mktemp -d)
104+trap 'rm -rf "$WORK"' EXIT INT TERM
105+
106+# Where the files to install come from: a release unpacked next to this script, this checkout
107+# built with cargo, or a release downloaded from GitHub.
108+SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
109+if [ "$FROM" = auto ]; then
110+ if [ -f "$SCRIPT_DIR/install.sh" ] && [ -x "$SCRIPT_DIR/budctl" ] && [ -x "$SCRIPT_DIR/budctl-gui" ]; then
111+ FROM=bundle
112+ else
113+ FROM=download
114+ fi
115+fi
116+
117+case "$FROM" in
118+ bundle)
119+ FILES=$SCRIPT_DIR
120+ DESKTOP=$SCRIPT_DIR/budctl.desktop
121+ ;;
122+ source)
123+ [ -f "$SCRIPT_DIR/Cargo.toml" ] || fail "--source needs a budctl checkout next to install.sh"
124+ command -v cargo >/dev/null 2>&1 || fail "cargo was not found; install Rust from https://rustup.rs/"
125+ say "Building budctl in $SCRIPT_DIR"
126+ cargo build --release --locked --manifest-path "$SCRIPT_DIR/Cargo.toml" -p budctl -p budctl-gui
127+ FILES=$SCRIPT_DIR/target/release
128+ DESKTOP=$SCRIPT_DIR/contrib/budctl.desktop
129+ ;;
130+ download)
131+ case "$(uname -m)" in
132+ x86_64 | amd64) TARGET=x86_64-unknown-linux-gnu ;;
133+ aarch64 | arm64) TARGET=aarch64-unknown-linux-gnu ;;
134+ *) fail "no release build for $(uname -m); build from a checkout with ./install.sh --source" ;;
135+ esac
136+ ARCHIVE=budctl-$TARGET.tar.gz
137+ if [ "$VERSION" = latest ]; then
138+ URL=https://github.com/$REPO/releases/latest/download
139+ else
140+ URL=https://github.com/$REPO/releases/download/v$VERSION
141+ fi
142+
143+ if command -v curl >/dev/null 2>&1; then
144+ fetch() { curl -fsSL --proto '=https' --tlsv1.2 -o "$2" "$1"; }
145+ elif command -v wget >/dev/null 2>&1; then
146+ fetch() { wget -q -O "$2" "$1"; }
147+ else
148+ fail "curl or wget is needed to download budctl"
149+ fi
150+
151+ say "Downloading $URL/$ARCHIVE"
152+ fetch "$URL/$ARCHIVE" "$WORK/$ARCHIVE" || fail "download failed: $URL/$ARCHIVE"
153+ fetch "$URL/$ARCHIVE.sha256" "$WORK/$ARCHIVE.sha256" || fail "download failed: $URL/$ARCHIVE.sha256"
154+ (cd "$WORK" && sha256sum -c --quiet "$ARCHIVE.sha256") || fail "checksum mismatch for $ARCHIVE"
155+ tar -xzf "$WORK/$ARCHIVE" -C "$WORK"
156+ FILES=$WORK/budctl-$TARGET
157+ DESKTOP=$FILES/budctl.desktop
158+ ;;
159+esac
160+
161+for name in budctl budctl-gui; do
162+ [ -x "$FILES/$name" ] || fail "$name was not found in $FILES"
163+done
164+[ -f "$DESKTOP" ] || fail "budctl.desktop was not found at $DESKTOP"
165+
166+# The launcher entry runs the binary by its full path, so it works even when the launcher's PATH
167+# lacks the install directory.
168+sed "s|^Exec=budctl-gui|Exec=$BIN_DIR/budctl-gui|" "$DESKTOP" >"$WORK/budctl.desktop"
169+
170+$SUDO install -d "$BIN_DIR" "$APP_DIR"
171+$SUDO install -m 0755 "$FILES/budctl" "$FILES/budctl-gui" "$BIN_DIR/"
172+$SUDO install -m 0644 "$WORK/budctl.desktop" "$APP_DIR/budctl.desktop"
173+
174+say "Installed $("$BIN_DIR/budctl" --version) to $BIN_DIR (budctl, budctl-gui)"
175+say "Installed the launcher entry to $APP_DIR/budctl.desktop"
176+case ":$PATH:" in
177+ *":$BIN_DIR:"*) ;;
178+ *) say "Note: $BIN_DIR is not on your PATH. Add it to run budctl from a terminal." ;;
179+esac