diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ee3bec1..2e55c92 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -13,7 +13,7 @@ body: attributes: label: Colony version description: Output of `colony --version` (or the release tag you downloaded). - placeholder: "v0.1.4" + placeholder: "0.9.2" validations: required: true diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index c1e7666..7af4b66 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -19,6 +19,14 @@ on: description: 'Release tag to publish to AUR (e.g. v0.7.0)' required: true type: string + secrets: + # Declared explicitly so the caller passes THIS secret and nothing else. + # Under `secrets: inherit` this job also received COLONY_SIGNING_KEY_PEM, + # which it has no use for - and that is the one secret whose compromise + # is unrecoverable, since every install in the field trusts the key it + # signs with. + AUR_SSH_PRIVATE_KEY: + required: true workflow_dispatch: inputs: tag: @@ -64,7 +72,18 @@ jobs: mkdir -p ~/.ssh printf '%s\n' "$SSH_KEY" > ~/.ssh/aur_bot chmod 600 ~/.ssh/aur_bot - ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null + # Pin the host key rather than trusting whatever answers today: + # a bare ssh-keyscan is trust-on-first-use on every single run. + # Fingerprint SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4, + # cross-checkable against the AUR entry on the Arch wiki. + AUR_HOSTKEY='aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN' + printf '%s\n' "$AUR_HOSTKEY" > ~/.ssh/known_hosts + # A mismatch means either a legitimate rotation (update the pin) or + # something worth stopping the release for. + if ! ssh-keyscan -t ed25519 aur.archlinux.org 2>/dev/null | grep -qxF "$AUR_HOSTKEY"; then + echo "::error::aur.archlinux.org host key does not match the pinned one" + exit 1 + fi - name: Clone AUR colony-bin repo run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61953a4..a6fb861 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,64 @@ jobs: - name: Format run: cargo fmt --all -- --check + # --all-targets, or clippy never sees the 140 tests - the code most + # likely to accumulate lint debt, since nobody reads it in review. - name: Clippy - run: cargo clippy -- -D warnings + run: cargo clippy --all-targets -- -D warnings - name: Test run: cargo test --verbose + + # The declared MSRV was 1.80 for years and could never have built: 70 + # transitive dependencies require more. Nothing checked it, because CI only + # ever ran on stable. This leg is what stops it drifting again. + msrv: + name: MSRV + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Read the declared MSRV + id: msrv + run: | + version=$(grep '^rust-version' Cargo.toml | tr -d ' "' | cut -d= -f2) + [ -n "$version" ] || { echo "::error::could not read rust-version from Cargo.toml"; exit 1; } + echo "version=$version" >> "$GITHUB_OUTPUT" + + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch + with: + toolchain: ${{ steps.msrv.outputs.version }} + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + key: msrv-${{ steps.msrv.outputs.version }} + + - name: Build on the declared floor + run: cargo check --locked --all-targets + + # Colony downloads and executes binaries, so a known-vulnerable dependency is + # not an abstract concern. This job GATES: red here means something new and + # actionable. + # + # It gates because the alternative does not work. Left to report without + # gating, it is red on every single PR forever - and a check that is always + # red is a check nobody reads. So the advisories that genuinely cannot be + # acted on from this repository are ignored BY ID, with the reason written + # down, and everything else fails the build. + # + # RUSTSEC-2026-0194 / RUSTSEC-2026-0195 (quick-xml): held at 0.38 by + # wayland-scanner, via winit and iced 0.14 - a semver-incompatible bump we + # cannot make from here. Both advisories concern parsing untrusted XML; + # wayland-scanner is a build-time proc-macro that parses the system's own + # Wayland protocol descriptions, so neither is reachable at runtime. + # Remove these two the day iced ships a winit new enough to carry + # quick-xml >= 0.41. + audit: + name: Advisories + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ignore: RUSTSEC-2026-0194,RUSTSEC-2026-0195 diff --git a/.github/workflows/colony-rust-release.yml.template b/.github/workflows/colony-rust-release.yml.template index 92726c2..6c3b910 100644 --- a/.github/workflows/colony-rust-release.yml.template +++ b/.github/workflows/colony-rust-release.yml.template @@ -1,94 +1,36 @@ -# Colony Rust App — Release Workflow Template +# Colony Rust program — release workflow # -# Copy this file to your repo as .github/workflows/release.yml -# Replace {{APP_NAME}} with your binary/repo name (lowercase). +# ┌─────────────────────────────────────────────────────────────────────────┐ +# │ THIS FILE IS NOT THE TEMPLATE. The canonical one lives in │ +# │ │ +# │ Project-Colony-Resources / templates/release.yml │ +# │ https://github.com/Project-Colony/Project-Colony-Resources │ +# │ │ +# │ Copy THAT file to your repo as .github/workflows/release.yml. │ +# └─────────────────────────────────────────────────────────────────────────┘ # -# Assets follow the Colony naming convention: -# {{APP_NAME}}-linux, {{APP_NAME}}-windows.exe, -# {{APP_NAME}}-macos, {{APP_NAME}}-macos-x86 +# Why this is a pointer and not a copy: it used to be a copy, and the two +# drifted. The Resources template referenced a secret named +# COLONY_SIGNING_KEY while every real repo uses COLONY_SIGNING_KEY_PEM, and it +# skipped signing on Windows entirely - so a program following it shipped an +# unsigned .exe that Colony then refused to install, because the manifest said +# "signed": true. Neither mistake was visible from either side. One canonical +# file, kept next to the shared script it calls, is what stops that recurring. # -# Colony auto-detects platforms from these asset names, so your -# colony.json only needs: { "name": "...", "category": "..." } - -name: Release - -on: - push: - branches: [main] - -permissions: - contents: write - pull-requests: write - -env: - CARGO_TERM_COLOR: always - -jobs: - release-please: - runs-on: ubuntu-latest - outputs: - release_created: ${{ steps.release.outputs.release_created }} - tag_name: ${{ steps.release.outputs.tag_name }} - upload_url: ${{ steps.release.outputs.upload_url }} - steps: - - uses: googleapis/release-please-action@v4 - id: release - with: - release-type: rust - - build: - name: Build ${{ matrix.asset }} - needs: release-please - if: ${{ needs.release-please.outputs.release_created }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - asset: "{{APP_NAME}}-linux" - - target: x86_64-pc-windows-msvc - os: windows-latest - asset: "{{APP_NAME}}-windows.exe" - - target: aarch64-apple-darwin - os: macos-latest - asset: "{{APP_NAME}}-macos" - - target: x86_64-apple-darwin - os: macos-latest - asset: "{{APP_NAME}}-macos-x86" - - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - with: - key: ${{ matrix.target }} - - # Uncomment if your app needs GTK/X11 dependencies on Linux: - # - name: Install Linux dependencies - # if: runner.os == 'Linux' - # run: | - # sudo apt-get update - # sudo apt-get install -y libgtk-3-dev libxdo-dev libdbus-1-dev - - - name: Build - run: cargo build --release --target ${{ matrix.target }} - - - name: Rename binary (Unix) - if: runner.os != 'Windows' - run: cp target/${{ matrix.target }}/release/{{APP_NAME}} ${{ matrix.asset }} - - - name: Rename binary (Windows) - if: runner.os == 'Windows' - run: copy target\${{ matrix.target }}\release\{{APP_NAME}}.exe ${{ matrix.asset }} - - - name: Upload binary to GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.release-please.outputs.tag_name }} - files: ${{ matrix.asset }} +# What you need, in short: +# +# 1. colony.json at your repo root (name + category is usually the whole +# file). Validate it with: colony validate-manifest colony.json +# 2. templates/release.yml -> .github/workflows/release.yml +# 3. templates/sign-release.sh -> scripts/sign-release.sh (only if you set +# "signed": true; the workflow calls it from there) +# 4. The COLONY_SIGNING_KEY_PEM organisation secret, which is restricted to +# Project-Colony repositories. +# +# The full contract - asset naming, the manifest, signing, the .meta sidecar, +# key rotation, and how to adopt signatures in a program that already ships - +# is in Project-Colony-Resources / design/releases.md. +# +# Colony's own release pipeline (.github/workflows/release-please.yml) is the +# reference implementation and stays separate: it self-updates, so its +# signature verification is mandatory rather than opt-in. diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 4434b53..05e6dc7 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -24,6 +24,24 @@ jobs: with: release-type: rust + # release-please's own step PUBLISHES the release - and moves + # /releases/latest - before a single binary exists. For the whole + # build+sign window every running Colony therefore showed an update badge + # whose every click failed: a missing asset first, then a fail-closed + # signature refusal once `build` had uploaded but `sign` had not. And if + # any build leg failed, that state was permanent until someone noticed. + # + # Held as a draft here rather than via a release-please config file: the + # config-file route means switching the action to manifest mode, which + # changes the outputs contract that the build, sign and aur jobs all + # depend on and that nine releases have proven. This is one gh call, and + # it narrows the window from minutes to the second between the two steps. + - name: Hold the release as a draft until it is signed + if: ${{ steps.release.outputs.release_created }} + env: + GH_TOKEN: ${{ github.token }} + run: gh release edit "${{ steps.release.outputs.tag_name }}" --draft=true + build: name: Build ${{ matrix.asset }} needs: release-please @@ -74,6 +92,31 @@ jobs: if: runner.os == 'Windows' run: copy target\${{ matrix.target }}\release\colony.exe ${{ matrix.asset }} + # The pipeline's signature checks prove provenance and say nothing about + # whether the binary RUNS. An asset built for the wrong architecture, or + # one that aborts under the release profile's LTO, would be signed, + # verified, published and hashed into the AUR PKGBUILD with every check + # green. `--version` exits before opening a window, so this is a genuine + # end-to-end smoke test of the shipped artefact. + - name: Smoke-test the artefact + if: matrix.target != 'x86_64-apple-darwin' + shell: bash + run: | + chmod +x "${{ matrix.asset }}" || true + "./${{ matrix.asset }}" --version + + # The Intel macOS asset is cross-compiled on an arm64 runner and cannot + # be executed there, so assert the architecture instead - which is the + # regression that would otherwise survive longest, on the leg with the + # fewest users. + - name: Check the cross-compiled artefact's architecture + if: matrix.target == 'x86_64-apple-darwin' + shell: bash + run: | + file "${{ matrix.asset }}" + file "${{ matrix.asset }}" | grep -q 'x86_64' \ + || { echo "::error::${{ matrix.asset }} is not an x86_64 binary"; exit 1; } + - name: Upload binary to GitHub Release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: @@ -159,6 +202,13 @@ jobs: done echo "published release verified" + # Only now does the release become visible. Until this step, /releases/ + # latest still points at the previous version, so no client is offered an + # update it cannot apply, and the README's download link never points at + # an empty release. + - name: Publish the release + run: gh release edit "$TAG" --draft=false + # Chained here (not on `release: published`) because release-please creates # the release with the default GITHUB_TOKEN, whose events do not trigger # other workflows - the standalone trigger never fired once in nine @@ -171,4 +221,7 @@ jobs: uses: ./.github/workflows/aur-publish.yml with: tag: ${{ needs.release-please.outputs.tag_name }} - secrets: inherit + # NOT `inherit`: the AUR job needs one secret, and inheriting handed it + # the release signing key as well. + secrets: + AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f810bf..cd1dbe0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Minimal form: } ``` -Valid categories: `Development`, `Graphics`, `Network`, `Office`, `Multimedia`, `System`, `Utilities`, `Games`, `Other`. +Valid categories: `Development`, `Graphics`, `Network`, `Office`, `Multimedia`, `System`, `Utilities`, `Security`, `Games`, `Other`. **App icon (recommended):** put a square PNG at `assets/icons/icon.png` and declare it in the manifest — `"icon": "assets/icons/icon.png"`. Colony shows it in the grid; without it your app gets a generated category tile. The same folder is the standard home for your `icon.ico` (Windows) and `icon.icns` (macOS). See the [App icon](docs/colony-spec.md#app-icon) section of the spec. @@ -46,7 +46,7 @@ You can upload any subset — Colony only advertises the platforms whose asset i ### Step 3 — Wire up a release workflow (Rust apps) -A ready-to-use GitHub Actions template is in this repo at [`.github/workflows/colony-rust-release.yml.template`](.github/workflows/colony-rust-release.yml.template). Copy it to your own repo as `.github/workflows/release.yml` and replace `{{APP_NAME}}` with your binary/repo name (lowercase). +The ready-to-use GitHub Actions template lives in Project-Colony-Resources at [`templates/release.yml`](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/templates/release.yml), next to the shared signing script it calls. Copy it to your own repo as `.github/workflows/release.yml` and replace `{{APP_NAME}}` with your binary/repo name (lowercase). It uses [`release-please`](https://github.com/googleapis/release-please) so every merged PR tagged with a conventional-commit prefix (`feat:`, `fix:`, etc.) opens a release PR; merging that PR tags the version, builds the matrix of 4 platforms, and uploads the assets under the convention above. Zero manual release work afterwards. @@ -73,7 +73,7 @@ Your app card should appear in the category you declared. If not: - Make sure the release is **published** (not a draft). - Make sure asset names match the convention (lowercase, no version in the name, correct extension). -- Look in `~/.cache/colony/repos_cache.json` — if your repo is there but without platforms, the `colony.json` or the asset names are wrong. See the [spec](docs/colony-spec.md) for the precise validation rules. +- Look in `~/.config/Colony/Colony/cache/repos_cache.json` - if your repo is there but without platforms, the `colony.json` or the asset names are wrong. See the [spec](docs/colony-spec.md) for the precise validation rules. - Verify you aren't hitting the GitHub rate limit: Settings → GitHub → Connect. --- @@ -96,7 +96,7 @@ cargo build --release # optimized build **Linux runtime dependencies** (for building and running): ``` -libgtk-3-dev libxdo-dev libdbus-1-dev libasound2-dev libglib2.0-dev pkg-config +libgtk-3-dev libxdo-dev libdbus-1-dev pkg-config ``` ### Code organization @@ -113,7 +113,7 @@ See [docs/architecture.md](docs/architecture.md) for the full layout. Short vers - `src/scan.rs` — system app detection (Linux `.desktop`, Windows Start Menu, macOS `.app`). - `src/sections.rs` — categories + filter logic. - `src/ui/` — widgets and panels (sidebar, app grid, detail view, settings). -- `src/ui/theme.rs` — all 24 theme families + 50+ palettes. +- `src/ui/theme.rs` — all 25 theme families + 57 palettes. ### Style @@ -141,7 +141,7 @@ See [docs/architecture.md](docs/architecture.md) for the full layout. Short vers ### Reporting bugs / feature requests -Use the templates in `.github/ISSUE_TEMPLATE/` when filing. Include Colony version (`colony --version`), OS, and relevant log excerpts from `~/.cache/colony/`. +Use the templates in `.github/ISSUE_TEMPLATE/` when filing. Include Colony version (`colony --version`), OS, and relevant log excerpts from `~/.cache/colony/colony.log`. --- diff --git a/Cargo.lock b/Cargo.lock index 1f7d3af..e2b6868 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "byteorder-lite" version = "0.1.0" @@ -841,7 +847,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -971,7 +977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1428,9 +1434,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -2031,9 +2037,13 @@ version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ + "byteorder", "dbus-secret-service", "log", "openssl", + "security-framework 2.11.1", + "security-framework 3.6.0", + "windows-sys 0.60.2", "zeroize", ] @@ -2366,7 +2376,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3133,7 +3143,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -3378,7 +3388,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3462,6 +3472,42 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -3901,7 +3947,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4810,7 +4856,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f02689..ef4b272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,12 @@ name = "colony" version = "0.9.2" edition = "2021" license = "GPL-3.0-or-later" -# Enforced floor for the README's "Rust 1.80+" claim (std::sync::LazyLock). -rust-version = "1.80" +# The real floor, and it is enforced by CI (see .github/workflows/ci.yml). +# 1.80 was declared for years and could never have worked: 70 transitive +# dependencies require more, including iced, zip, image and wgpu at 1.88, so +# cargo refuses to build at all on it. Nothing checked, because CI only ever +# ran on stable. +rust-version = "1.88" [dependencies] anyhow = "1.0.100" @@ -19,7 +23,18 @@ futures = "0.3" open = "5" base64 = "0.23" dirs = "6" -keyring = { version = "3", features = ["sync-secret-service", "vendored"] } +# apple-native / windows-native are REQUIRED, not optional: keyring 3 has no +# default feature, and without them its macOS and Windows backends resolve to +# the in-memory `mock`, whose set_password returns Ok while storing nothing. +# Colony reads that Ok as success and deletes its plaintext fallback on purpose, +# so the token was written nowhere and every restart logged the user out. Both +# are target-gated in keyring's manifest, so they add nothing to the Linux build. +keyring = { version = "3", features = [ + "apple-native", + "windows-native", + "sync-secret-service", + "vendored", +] } semver = "1" sha2 = "0.10" # Pure-Rust ed25519 verification for signed launcher self-updates (no openssl). diff --git a/README.md b/README.md index 9bf7bbe..75719dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Colony is the central piece of [Project Colony](https://github.com/Project-Colon [![License: GPL-3.0-or-later](https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg)](LICENSE) [![AUR: colony-bin](https://img.shields.io/badge/AUR-colony--bin-blue)](https://aur.archlinux.org/packages/colony-bin) [![AUR: colony-git](https://img.shields.io/badge/AUR-colony--git-blue)](https://aur.archlinux.org/packages/colony-git) -[![Platforms](https://img.shields.io/badge/platforms-linux%20%7C%20windows%20%7C%20macOS-lightgrey)](#installation) +[![Platforms](https://img.shields.io/badge/platforms-linux%20%7C%20windows*%20%7C%20macOS*-lightgrey)](#platforms)