From 717c54c9c1d889fdaf7807653c895c95dab855ce Mon Sep 17 00:00:00 2001 From: nevaforget <> Date: Tue, 23 Jun 2026 16:03:35 +0200 Subject: [PATCH] chore: harden CI and shipped script, tidy internals (audit remediation) A four-axis audit found no HIGH/MEDIUM issues. This addresses the remaining low-risk items and protects the release path: - ci: add cargo test + clippy -D warnings gate on master/tags - ci: reject malformed version tags before the pkgver sed substitution - fix: move the shipped waybar wrapper's cache/lock to $XDG_RUNTIME_DIR (mode-700, no cross-user symlink) instead of predictable /tmp paths - refactor: find_card() returns the open ALSA mixer, dropping a second open; collapse two if-let blocks into edition-2024 let-chains - docs: record in DECISIONS.md why the audit's "dead code" stays (it mirrors the Bragi spec) and why the 5ms HID sleep is left as-is Bump 0.1.4 -> 0.1.5: the shipped waybar script changes behavior. --- .gitea/workflows/ci.yaml | 31 +++++++++++++++++++++++++++++ .gitea/workflows/update-pkgver.yaml | 4 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- DECISIONS.md | 6 ++++++ scripts/corsairctl-waybar.sh | 5 +++-- src/bragi/protocol.rs | 16 +++++++-------- src/sidetone.rs | 14 ++++++------- 8 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 .gitea/workflows/ci.yaml diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..ab7b61e --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,31 @@ +# ABOUTME: Test + lint gate on push to master and on version tags. +# ABOUTME: Stops a broken build or regressed test from reaching a tagged release. + +name: CI + +on: + push: + branches: + - master + tags: + - 'v*' + +jobs: + test: + runs-on: moonarch + steps: + - name: Checkout pushed commit + run: | + git clone http://gitea:3000/nevaforget/corsairctl.git repo + cd repo + git checkout "$GITHUB_SHA" + + - name: cargo test + run: | + cd repo + cargo test --locked + + - name: cargo clippy + run: | + cd repo + cargo clippy --all-targets -- -D warnings diff --git a/.gitea/workflows/update-pkgver.yaml b/.gitea/workflows/update-pkgver.yaml index 6c936a3..b0598a6 100644 --- a/.gitea/workflows/update-pkgver.yaml +++ b/.gitea/workflows/update-pkgver.yaml @@ -17,6 +17,10 @@ jobs: git clone --bare http://gitea:3000/nevaforget/corsairctl.git source.git cd source.git PKGVER=$(git describe --tags --abbrev=0 | sed 's/^v//') + if ! echo "$PKGVER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Invalid pkgver: $PKGVER" + exit 1 + fi echo "New pkgver: $PKGVER" echo "$PKGVER" > /tmp/pkgver diff --git a/Cargo.lock b/Cargo.lock index 6c087ed..60240e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "corsairctl" -version = "0.1.4" +version = "0.1.5" dependencies = [ "alsa", "clap", diff --git a/Cargo.toml b/Cargo.toml index adc3185..253f0b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "corsairctl" -version = "0.1.4" +version = "0.1.5" edition = "2024" description = "CLI tool for Corsair Bragi-protocol devices (HS80, etc.)" license = "MIT" diff --git a/DECISIONS.md b/DECISIONS.md index 986a20b..098df51 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -23,3 +23,9 @@ - **Why**: The open-source publish plan (rename away from "Corsair", multi-protocol backend layer, crates.io, upstream to HeadsetControl) was dropped. The tool is for personal use across Dom's own machines, distributed as an Arch package via the private moonarch repo (Gitea registry at gitea.moonarch.de), used primarily as a Waybar widget. - **Tradeoffs**: GPLv3 and the rename were only justified by public distribution and porting HeadsetControl (GPLv3) code. With no public release and no GPL-derived code in the tree, copyleft and the trademark rename buy nothing. MIT is simpler and reversible — switch to GPLv3 only if GPL code is later pulled in. - **How**: Revert license to MIT (Cargo.toml + MIT LICENSE file). Keep the name `corsairctl`. PLAN.md retained as a record of the considered direction, no longer driving work. + +## 2026-06-23 – Audit remediation: CI gate, script hardening; keep the protocol-spec model +- **Who**: Dom, F.R.I.D.A.Y. +- **Why**: A four-axis audit (quality/performance/security/hygiene) found no HIGH/MEDIUM issues; only a few low-risk items remained. Acted on the real ones, recorded what was deliberately left alone so a future audit does not re-flag it. +- **Tradeoffs**: The audit marked unused `Property` variants, the response endpoint constant, and `STATUS_OK`/`BragiResponse.status` as "dead code". Kept on purpose — the `Property` enum mirrors the full Bragi spec (`docs/bragi-protocol.md`) and `status` captures a real device byte; deleting would lose protocol fidelity for no functional gain. The 5 ms HID pre-read sleep was also left as-is — it is a deliberate fix against observed response mix-ups (`f3a4bf8`), not arbitrary latency. +- **How**: Added `ci.yaml` (`cargo test` + `clippy -D warnings` on master pushes and version tags) and a tag-format guard in `update-pkgver.yaml`. Moved the shipped Waybar wrapper's cache/lock from `/tmp` to `$XDG_RUNTIME_DIR` (mode-700, no cross-user symlink). `find_card()` now returns the already-open ALSA mixer instead of a card name, dropping a redundant second open. Collapsed two `if let` blocks into edition-2024 let-chains. diff --git a/scripts/corsairctl-waybar.sh b/scripts/corsairctl-waybar.sh index 60780a6..99b3c11 100755 --- a/scripts/corsairctl-waybar.sh +++ b/scripts/corsairctl-waybar.sh @@ -2,8 +2,9 @@ # ABOUTME: Waybar-Wrapper für corsairctl — cached JSON-Output. # ABOUTME: Verhindert dass mehrere Waybar-Bars gleichzeitig das HID-Gerät ansprechen. -CACHE="/tmp/corsairctl-waybar.json" -LOCK="/tmp/corsairctl-waybar.lock" +RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" +CACHE="$RUNTIME_DIR/corsairctl-waybar.json" +LOCK="$RUNTIME_DIR/corsairctl-waybar.lock" MAX_AGE=300 FALLBACK='{"text":"","tooltip":"","class":"offline","percentage":0}' diff --git a/src/bragi/protocol.rs b/src/bragi/protocol.rs index 6ceb178..4f0778a 100644 --- a/src/bragi/protocol.rs +++ b/src/bragi/protocol.rs @@ -138,15 +138,15 @@ pub fn parse_response_validated( } // Response-Korrelation: Endpoint und Command gegen die Anfrage prüfen - if let Some(exp) = expected_endpoint { - if endpoint != exp { - return Err(CorsairError::UnexpectedResponse { endpoint, command }); - } + if let Some(exp) = expected_endpoint + && endpoint != exp + { + return Err(CorsairError::UnexpectedResponse { endpoint, command }); } - if let Some(exp) = expected_command { - if command != exp { - return Err(CorsairError::UnexpectedResponse { endpoint, command }); - } + if let Some(exp) = expected_command + && command != exp + { + return Err(CorsairError::UnexpectedResponse { endpoint, command }); } let data = if raw.len() > 4 { diff --git a/src/sidetone.rs b/src/sidetone.rs index 4e81162..4c351b3 100644 --- a/src/sidetone.rs +++ b/src/sidetone.rs @@ -7,17 +7,19 @@ use crate::error::Result; const SIDETONE_CONTROL: &str = "Sidetone"; -/// Findet die ALSA-Card mit einem Sidetone-Control. +/// Findet die ALSA-Card mit einem Sidetone-Control und gibt den offenen Mixer zurück. /// /// Sucht nach der Karte, die ein "Sidetone" Control hat, /// statt sich auf den Kartennamen zu verlassen (der kann abgeschnitten sein). -fn find_card() -> Result { +/// Gibt den bereits geöffneten Mixer zurück, damit der Aufrufer ihn nicht +/// ein zweites Mal öffnen muss. +fn find_card() -> Result { for card in alsa::card::Iter::new().flatten() { let name = format!("hw:{}", card.get_index()); if let Ok(mixer) = Mixer::new(&name, false) { let selem_id = SelemId::new(SIDETONE_CONTROL, 0); if mixer.find_selem(&selem_id).is_some() { - return Ok(name); + return Ok(mixer); } } } @@ -26,8 +28,7 @@ fn find_card() -> Result { /// Liest den aktuellen Sidetone-Level (0-23). pub fn get_level() -> Result { - let card = find_card()?; - let mixer = Mixer::new(&card, false)?; + let mixer = find_card()?; let selem_id = SelemId::new(SIDETONE_CONTROL, 0); let selem = mixer @@ -45,8 +46,7 @@ pub fn get_level() -> Result { /// Setzt den Sidetone-Level (0-23). pub fn set_level(level: i64) -> Result<()> { - let card = find_card()?; - let mixer = Mixer::new(&card, false)?; + let mixer = find_card()?; // Volume setzen let volume_id = SelemId::new(SIDETONE_CONTROL, 0);