diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 791be8a..d312a8c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,12 +180,6 @@ jobs: # No build cache here: SignPath forbids release builds that reuse # outputs of earlier, unverified builds. - - 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 }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b940a0..0ccd393 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ Two types of contribution land here, each with a different path: -1. **Adding your app to the Colony catalog** — jump to [Adding your app to Colony](#adding-your-app-to-colony). -2. **Contributing to Colony itself** (the launcher) — jump to [Contributing to Colony itself](#contributing-to-colony-itself). +1. **Adding your app to the Colony catalog**: jump to [Adding your app to Colony](#adding-your-app-to-colony). +2. **Contributing to Colony itself** (the launcher): jump to [Contributing to Colony itself](#contributing-to-colony-itself). --- @@ -14,24 +14,25 @@ Colony automatically scans **all public repositories** under the `Project-Colony 1. A `colony.json` manifest sits at the repo root. 2. A GitHub release exists with assets matching the naming convention below. -### Step 1 — Prepare a `colony.json` +### Step 1: Prepare a `colony.json` Minimal form: ```json { + "$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json", "name": "YourApp", - "category": "Utilities" + "category": "utility" } ``` -Valid categories: `Development`, `Graphics`, `Network`, `Office`, `Multimedia`, `System`, `Utilities`, `Security`, `Games`, `Other`. +Valid categories: `development`, `graphics`, `network`, `office`, `multimedia`, `system`, `utility`, `security`, `game`, `other` (case-insensitive; `utilities` and `games` are accepted too). The `$schema` line lets your editor check the file as you type. -**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. +**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/guide/colony-spec.md#app-icon) section of the spec. -For the full reference (pinned releases, explicit platforms, archive extraction, SHA256, pattern-matched asset names, etc.), read [docs/colony-spec.md](docs/colony-spec.md). +For the full reference (pinned releases, explicit platforms, archive extraction, SHA256, pattern-matched asset names, etc.), read [docs/guide/colony-spec.md](docs/guide/colony-spec.md). -### Step 2 — Publish release assets +### Step 2: Publish release assets Colony auto-detects platforms from release asset filenames using the pattern `-`: @@ -42,17 +43,17 @@ Colony auto-detects platforms from release asset filenames using the pattern `` says which. See the [spec](docs/guide/colony-spec.md) for the precise validation rules. +- Verify you aren't hitting the GitHub rate limit: click **GitHub** in Colony's sidebar, then **Sign in with GitHub**. --- @@ -93,27 +94,23 @@ cargo test # run the unit tests (manifest parsing, scanning, i18n, cargo build --release # optimized build ``` -**Linux runtime dependencies** (for building and running): - -``` -libgtk-3-dev libxdo-dev libdbus-1-dev pkg-config -``` +You need Rust 1.88 or newer (the `rust-version` floor, checked by CI) and a C compiler. No system libraries are needed: on Linux, the D-Bus client used for the Secret Service is compiled from source. ### Code organization -See [docs/architecture.md](docs/architecture.md) for the full layout. Short version: +See [docs/internals/architecture.md](docs/internals/architecture.md) for the full layout. Short version: - `src/main.rs` - entry point: CLI flags, startup order, the iced launch. - `src/app.rs` - Elm-architecture `view()`, `subscription()` and `theme()`; `src/update/` - `Message` handlers. - `src/github/` - GitHub API layer: manifest fetching, release asset resolution, update checks. - `src/download/` - release/asset downloads, archive extraction, launcher self-update. -- `src/signing.rs` - ed25519 verification of signed launcher updates (see [docs/release-signing.md](docs/release-signing.md)). +- `src/signing.rs` - ed25519 verification of launcher updates and app releases (see [docs/internals/release-signing.md](docs/internals/release-signing.md)). - `src/persistence.rs` - data directories, preferences, install state, on-disk caches, favorites. - `src/config.rs` - locating external config files (`categories.json`, `colony.toml`). - `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, Preferences), on colony-ui's kit. -- `src/ui/theme.rs` — a re-export of [colony-ui](https://crates.io/crates/colony-ui) +- `src/ui/theme.rs`: a re-export of [colony-ui](https://crates.io/crates/colony-ui) plus Colony's own button styling. The 26 theme families and 59 palettes live in that crate, generated from the design tokens in [Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources). @@ -123,15 +120,15 @@ See [docs/architecture.md](docs/architecture.md) for the full layout. Short vers - Rust edition 2021, `cargo fmt` before commit (default rustfmt config; CI enforces `cargo fmt --check`). - Prefer small focused commits with [Conventional Commits](https://www.conventionalcommits.org) prefixes (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, `ci:`) so release-please can detect release-worthy changes and bump versions automatically. - Keep platform-specific code behind `#[cfg(target_os = "...")]` so every platform still compiles. -- If you touch the public `colony.json` schema, also update [docs/colony-spec.md](docs/colony-spec.md) in the same PR. +- If you touch the public `colony.json` schema, also update [docs/guide/colony-spec.md](docs/guide/colony-spec.md) in the same PR. ### Before you open a PR 1. `cargo fmt` -2. `cargo clippy -- -D warnings` (treat warnings as errors — CI does) +2. `cargo clippy --all-targets -- -D warnings` (treat warnings as errors, as CI does) 3. `cargo test` 4. `cargo build --release` (catches linker issues absent in debug) -5. Sync with `main` (merge or rebase) if it has advanced — every PR is squash-merged, so your branch collapses into a single commit on merge. +5. Sync with `main` (merge or rebase) if it has advanced: every PR is squash-merged, so your branch collapses into a single commit on merge. ### What kind of change @@ -140,7 +137,7 @@ See [docs/architecture.md](docs/architecture.md) for the full layout. Short vers | Typo / doc clarification | PR directly. | | Bug fix | PR directly, reference the issue if any. | | Small feature (sidebar tweak, new setting) | PR directly, include before/after screenshots if UI-visible. | -| A new theme family | Not here — add the TOML to [Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources), regenerate, and bump `colony-ui`. Colony needs no change at all. | +| A new theme family | Not here: add the TOML to [Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources), regenerate, and bump `colony-ui`. Colony needs no change at all. | | Large feature (new auth mode, protocol change) | Open an issue first to discuss design. | ### Reporting bugs / feature requests diff --git a/Cargo.lock b/Cargo.lock index 8413993..18de6ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "ahash" version = "0.8.12" @@ -95,6 +106,17 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "apple-native-keyring-store" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b350bfd03649e07aa05c0a81b3e15934374e585c98204a57e20b9d49f49bb9a" +dependencies = [ + "keyring-core", + "log", + "security-framework", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -327,6 +349,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.12.1" @@ -336,6 +367,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -461,6 +501,15 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.6.0" @@ -492,10 +541,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.1", "rand_core", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -560,21 +619,23 @@ name = "colony" version = "0.10.4" dependencies = [ "anyhow", + "apple-native-keyring-store", "base64", "colony-ui", + "dbus-secret-service-keyring-store", "dirs 7.0.0", "ed25519-dalek", "flate2", "futures", "iced", "image", - "keyring", + "keyring-core", "open", "reqwest", "semver", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "shell-words", "tar", "tempfile", @@ -582,6 +643,7 @@ dependencies = [ "toml", "tracing", "tracing-subscriber", + "windows-native-keyring-store", "winresource", "zip", ] @@ -723,6 +785,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.1" @@ -766,6 +837,16 @@ dependencies = [ "wgpu", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-common" version = "0.2.2" @@ -797,9 +878,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.1", "curve25519-dalek-derive", - "digest", + "digest 0.11.3", "fiat-crypto", "rustc_version", "subtle", @@ -833,20 +914,49 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" dependencies = [ + "aes", + "block-padding", + "cbc", "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", "openssl", + "sha2 0.10.9", "zeroize", ] +[[package]] +name = "dbus-secret-service-keyring-store" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7227ffd3b4896266bbc100348025cebe51d9aea94ce118a1d68f4a6463963dae" +dependencies = [ + "dbus-secret-service", + "keyring-core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "digest" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer", + "block-buffer 0.12.1", "const-oid", - "crypto-common", + "crypto-common 0.2.2", ] [[package]] @@ -876,7 +986,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -965,7 +1075,7 @@ checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ "curve25519-dalek", "ed25519", - "sha2", + "sha2 0.11.0", "subtle", ] @@ -1023,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1336,6 +1446,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "1.1.0" @@ -1579,6 +1699,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "http" version = "1.5.0" @@ -2005,6 +2143,16 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "ipnet" version = "2.12.2" @@ -2125,19 +2273,12 @@ dependencies = [ ] [[package]] -name = "keyring" -version = "3.6.3" +name = "keyring-core" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +checksum = "fb1e621458ca9c51aa110bd0339d4751a056b9576bf1253aee1aa560dda0fc9d" dependencies = [ - "byteorder", - "dbus-secret-service", "log", - "openssl", - "security-framework 2.11.1", - "security-framework 3.7.0", - "windows-sys 0.60.2", - "zeroize", ] [[package]] @@ -2486,7 +2627,70 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", ] [[package]] @@ -3258,7 +3462,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3385,6 +3589,18 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.18" @@ -3516,7 +3732,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3542,7 +3758,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", + "security-framework", ] [[package]] @@ -3570,10 +3786,10 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3649,19 +3865,6 @@ 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.13.2", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -3773,6 +3976,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.11.0" @@ -3780,8 +3994,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -3963,7 +4177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4140,7 +4354,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.5", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4486,7 +4700,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5022,7 +5236,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5046,7 +5260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5080,7 +5294,7 @@ dependencies = [ "windows-interface 0.58.0", "windows-result 0.2.0", "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5157,6 +5371,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-native-keyring-store" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063426e76fdec7438d56bb777f67e318a84a25c707b07e575cb8b78e10c028f8" +dependencies = [ + "byteorder", + "keyring-core", + "regex", + "windows-sys 0.61.2", + "zeroize", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -5184,7 +5411,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5203,7 +5430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5221,7 +5448,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5230,16 +5457,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -5257,31 +5475,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -5299,96 +5500,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winit" version = "0.30.13" diff --git a/Cargo.toml b/Cargo.toml index eaa5e47..5fdec33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,18 +32,15 @@ futures = "0.3.34" open = "5.4.4" base64 = "0.23.1" dirs = "7.0.0" -# 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.6.3", features = [ - "apple-native", - "windows-native", - "sync-secret-service", - "vendored", -] } +# The GitHub token goes to the OS credential store through keyring 4, which is +# keyring-core plus one store crate per OS (the target tables below). keyring 3 +# fell back to an in-memory mock whenever a platform feature was missing: its +# set_password returned Ok while storing nothing, Colony deleted the plaintext +# fallback on that Ok, and every restart logged the user out. keyring-core has +# no implicit store at all. Colony never sets a default store and builds every +# entry from the per-target store in src/oauth.rs, so the mock is reachable only +# from the tests, and the plaintext copy goes only after the token reads back. +keyring-core = "1.0.0" semver = "1.0.28" sha2 = "0.11.0" # Pure-Rust ed25519 verification for signed launcher self-updates (no openssl). @@ -57,6 +54,21 @@ image = { version = "0.25.10", default-features = false, features = ["png"] } tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +# One credential store per OS, each gated to its own target so a build pulls in +# only its own. The stores use the same service, account and Windows target name +# as keyring 3, so a token saved before the upgrade is still read. +[target.'cfg(target_os = "macos")'.dependencies] +apple-native-keyring-store = { version = "1.0.2", features = ["keychain"] } + +[target.'cfg(windows)'.dependencies] +windows-native-keyring-store = "1.1.0" + +# Secret Service over libdbus, as with keyring 3. `vendored` builds libdbus from +# source so no system headers are needed; `crypto-rust` encrypts the session +# without openssl. +[target.'cfg(all(unix, not(any(target_os = "macos", target_os = "ios", target_os = "android"))))'.dependencies] +dbus-secret-service-keyring-store = { version = "1.0.1", features = ["crypto-rust", "vendored"] } + [profile.release] lto = "thin" codegen-units = 1 diff --git a/README.md b/README.md index b874f09..947b44a 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,148 @@ -# Colony +
-**The hub for the Colony ecosystem.** Browse, install, update, and launch every Colony app from a single, lightweight interface. + + + Colony + -Colony is the central piece of [Project Colony](https://github.com/Project-Colony) — an ecosystem of small, focused desktop utilities built with Rust. Instead of one monolithic tool that does everything poorly, Colony curates a growing collection of apps, each designed to do one thing exceptionally well. +**The launcher and app store for Project Colony: browse, install, update and launch every Colony app from one window, on Linux, Windows and macOS.** + +
[![License: GPL-3.0-or-later](https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg)](LICENSE) +[![Platforms](https://img.shields.io/badge/platforms-linux%20%7C%20windows%20%7C%20macOS-lightgrey)](#installation) [![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)](#platforms) - - +[Project Colony](https://github.com/Project-Colony) ships small desktop programs, +one GitHub repository and one set of release files each. Getting them by hand +means watching every repository, picking the right file for your platform, +checking its signature, and doing it all again at every update. Colony does that +for the whole organisation: it lists every app, installs the build for your +platform once its signature checks out, keeps it up to date, and launches it next +to the applications already on your system. + +> **Status:** Colony is developed and used on Linux, where it installs, updates +> and launches the Project Colony apps. Its self-update is fail-closed: it +> applies only a release whose ed25519 signature and signed `.meta` check out, +> and never an older version than the one running. CI builds and tests every +> change on Linux, Windows and macOS, and checks that it still builds with Rust +> 1.88. The Windows and macOS builds work but are best-effort (see +> [Platforms](#platforms)). Releases now go through the organisation's shared +> sign-and-publish workflow, which has not yet published a Colony release: the +> latest, v0.10.4, was signed by the workflow it replaced. Authenticode signing +> of the Windows build through SignPath is applied for and not active yet. Every +> Project Colony program's release downloads the latest `colony-linux`, verifies +> its signatures and runs its `validate-manifest`, so a broken Colony release +> blocks the releases of every other program. + +## Why Colony + +Without it, each Colony app is a GitHub release page to check by hand. With it: + +- **One catalog, nothing to register.** Every repository of the Project-Colony + organisation with a `colony.json` at its root appears in Colony, with its + README, changelog and licence, as soon as it publishes a release. +- **Checked before it runs.** Colony verifies a release's ed25519 signature and + its signed `.meta`, which binds the file to its name, digest and version, and + refuses a release older than the one installed. Once an app has installed + with a valid signature, a later unsigned release of it is refused. +- **Every update in one place.** Colony compares each installed app, and itself, + with its latest release, and updates in one click. An interrupted download + resumes where it stopped. +- **Your other applications too.** It lists the `.desktop` entries on Linux, the + Start Menu on Windows and `/Applications` on macOS, so everything launches + from the same grid. +- **No account needed.** Signing in with GitHub is optional and only raises the + API rate limit. ---- +``` +Project-Colony on GitHub Colony your machine + /colony.json --> catalog, README, changelog + release files --> check .sig and signed .meta --> Colony/apps// --> launch + Colony release --> self-update, never older --> colony (replaced in place) +``` + +## What it does + +- **Discover**: browse the catalog by category, search by name, and read each + app's README, changelog and licence without leaving Colony. +- **Install and update**: one click installs the build for your platform, one + click updates it. A badge shows when a new version is out, for apps and for + Colony itself. +- **Launch**: Colony apps and the applications already installed on your system, + side by side. On Linux, every app Colony installs also gets a menu entry, so + your desktop's own launcher finds it too. +- **Self-update**: Colony downloads its new version, verifies it, swaps itself in + and rolls back if the swap fails. An AUR install is updated by pacman instead. +- **Preferences**: click **Colony** at the top of the sidebar for the theme and + accent colour, text size, high contrast, a dyslexia-friendly font, reduced + motion, English or French, keyboard navigation, and the storage and update + settings. + +New to Colony? The [tutorial](docs/guide/tutorial.md) walks through a first +install. ## Installation ### Arch Linux (AUR) -Two variants maintained on the AUR. Pick **one**: +Two packages, pick **one**: + +| Package | Install | Notes | +|---|---|---| +| `colony-bin` | `paru -S colony-bin` | The released binary, about 30 MB to download. Updated at every Colony release. | +| `colony-git` | `paru -S colony-git` | Built from the latest commit, about 5 minutes. Rebuilt on `paru -Syu` when the repository has moved. | -| Package | Install | Notes | -|---------------|------------------------------|------------------------------------------------| -| `colony-bin` | `paru -S colony-bin` | Prebuilt binary, instant install (~30 MB DL). Auto-updates at every upstream release. | -| `colony-git` | `paru -S colony-git` | Builds from HEAD, ~5 min compile, always at latest commit. Recompiles on each `paru -Syu` if upstream advanced. | +Both install `/usr/bin/colony` and a `colony.desktop` entry, so GNOME, KDE, +rofi and wofi find it. -Both provide a `/usr/bin/colony` binary and a `colony.desktop` entry so GNOME/KDE/rofi/wofi launchers pick it up automatically. +### Direct binary download -### Direct binary download (Linux / Windows / macOS) +Grab the file for your platform from the +[latest release](https://github.com/Project-Colony/Colony/releases/latest): -Grab the single-file executable for your platform from the [latest release](https://github.com/Project-Colony/Colony/releases/latest): +| Platform | Asset | +|---|---| +| Linux (x86_64) | `colony-linux` | +| Windows (x86_64) | `colony-windows.exe` | +| macOS (Apple Silicon) | `colony-macos` | +| macOS (Intel) | `colony-macos-x86` | -| Platform | Asset | -|-----------------------|--------------------------| -| Linux (x86_64) | `colony-linux` | -| Windows (x86_64) | `colony-windows.exe` | -| macOS (Apple Silicon) | `colony-macos` | -| macOS (Intel) | `colony-macos-x86` | +```bash +chmod +x colony-linux && ./colony-linux +``` -No installer - download, `chmod +x` on Unix, and run. On macOS, Gatekeeper -quarantines anything fetched from a browser, so clear the flag first: -`xattr -d com.apple.quarantine colony-macos`. A new release is published automatically by `release-please` after each merged change, so the latest binary is always at `/releases/latest`. +There is no installer. On macOS, Gatekeeper quarantines anything downloaded with +a browser, so clear the flag first: `xattr -d com.apple.quarantine colony-macos`. +To check a download yourself, see [Code signing policy](#code-signing-policy). +A new version is published when its release is cut, not after every change, and +`/releases/latest` always points at the newest one. ### Build from source ```bash -git clone https://github.com/Project-Colony/Colony.git +git clone https://github.com/Project-Colony/Colony cd Colony cargo build --release ./target/release/colony ``` -Requires Rust 1.88+ and, on Linux, `libgtk-3-dev`, `libxdo-dev`, `libdbus-1-dev`, `pkg-config`. - ---- - -## What Colony does - -- **Discover** — Browse all Colony apps by category, search by name, read descriptions, changelogs, and licenses without leaving the launcher. -- **Install & Update** — One click to download, one click to update. Colony tracks versions and shows when something new is available — for apps and for itself. -- **Launch** - Colony also detects every application already installed on your system (`.desktop` files on Linux, the Start Menu on Windows, `/Applications` on macOS) and lets you launch them alongside Colony apps. -- **Self-update** — Colony keeps itself up to date. When a new version is available, a badge appears in the sidebar; click to download, then restart. - -➡️ New user? Start with the **[Tutorial](docs/tutorial.md)** for a step-by-step walkthrough. +Requires Rust 1.88 or newer and a C compiler. No system libraries are needed: +on Linux, the D-Bus client Colony uses for the Secret Service is compiled from +source. ## Design principles -**Single purpose, native performance.** Colony and every app in its ecosystem follow the same philosophy: +**Single purpose, native performance.** Colony and every app in its ecosystem +follow the same philosophy: -- **Rust-native** — Built with [Iced](https://iced.rs). Startup is instant, memory usage is minimal, and your CPU stays cool. -- **Async everything** — Network calls, file I/O, and scanning run in the background. The UI never freezes. -- **Zero configuration needed** — Colony works out of the box. Scan directories, sections, and themes are all configurable, but sensible defaults are provided. +- **Rust-native**: built with [Iced](https://iced.rs). Startup is instant, + memory use is small, and your CPU stays cool. +- **Async everything**: network calls, file I/O and scanning run in the + background. The UI never freezes. +- **Zero configuration needed**: Colony works out of the box. Scan directories, + sidebar sections and themes can all be changed, but the defaults are sensible. ## Theming @@ -92,26 +161,36 @@ Project Colony program, so a theme looks the same wherever you meet it: | Horizon | Mélange | Synthwave '84 | Modus (Operandi, Vivendi) | | Stellar Blade (Eve, Tachy, Lily, Enya, Kaya; fan-made, unofficial) | Parchment | | | -Each palette includes full semantic tokens: backgrounds, text layers, accents, success/warning/error states, button states, and more. +Each palette includes full semantic tokens: backgrounds, text layers, accents, +success, warning and error states, button states, and more. ## For app developers -Want your Rust (or any) desktop app to appear in Colony's catalog? It takes a single JSON file plus a GitHub release with properly named assets. Colony only scans repositories of the [Project-Colony](https://github.com/Project-Colony) GitHub organization, so the repo has to live there to be listed. +Want your desktop app to appear in Colony's catalog? It takes one JSON file plus +a GitHub release with properly named assets. Colony only lists repositories of +the [Project-Colony](https://github.com/Project-Colony) GitHub organisation, so +the repository has to live there. -**Quick version** — add `colony.json` at the root of your repo: +**Quick version**: add `colony.json` at the root of your repository: ```json { + "$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json", "name": "YourApp", - "category": "Utilities" + "category": "utility" } ``` -Then publish a GitHub Release with assets named `yourapp-linux`, `yourapp-windows.exe`, `yourapp-macos`, `yourapp-macos-x86`. Colony picks them up automatically. +Then publish a GitHub release with assets named `yourapp-linux`, +`yourapp-windows.exe`, `yourapp-macos` and `yourapp-macos-x86`. Colony picks +them up automatically. -**Full walkthrough**: see [CONTRIBUTING.md § Adding your app to Colony](CONTRIBUTING.md#adding-your-app-to-colony). +**Full walkthrough**: [CONTRIBUTING.md, Adding your app to Colony](CONTRIBUTING.md#adding-your-app-to-colony), +and the [colony.json reference](docs/guide/colony-spec.md). -The canonical [release workflow template](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/templates/sign-and-publish-caller.yml) lives in Project-Colony-Resources, alongside the shared signing workflow it calls and the manifest schema. +The canonical [release workflow template](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/templates/sign-and-publish-caller.yml) +lives in Project-Colony-Resources, alongside the shared signing workflow it calls +and the manifest schema. ## Platforms @@ -121,32 +200,60 @@ far less exercise, and some desktop integration is Linux-only. | Platform | Architecture | Status | Notes | |---|---|---|---| -| Linux | x86_64 | Supported | Primary platform. `.desktop` entries for installed apps, `.desktop` scan of local apps. | +| Linux | x86_64 | Supported | Primary platform. Menu entries for installed apps, `.desktop` scan of local apps. | | Windows | x86_64 | Best-effort | Installs, updates and launches. No Start Menu shortcut is created for installed apps. | | macOS | ARM (Apple Silicon) | Best-effort | Installs, updates and launches single-binary apps. `.app` bundles are not supported, and nothing is registered with Launch Services. | | macOS | x86_64 (Intel) | Best-effort | As above. | "Best-effort" means bug reports are welcome and will be fixed, but the platform is not part of the routine test loop. If you use Colony on Windows or macOS and -something is wrong, please [open an issue](https://github.com/Project-Colony/Colony/issues): that is -how these move up. +something is wrong, please [open an issue](https://github.com/Project-Colony/Colony/issues): +that is how these move up. ## Documentation -| Document | Purpose | -|------------------------------------|-------------------------------------------------------| -| [Tutorial](docs/tutorial.md) | End-user walkthrough: install → first app → launch | -| [FAQ](docs/faq.md) | Common questions + troubleshooting | -| [Architecture](docs/architecture.md) | Internal structure, tech stack, data flow | -| [Release signing](docs/release-signing.md) | ed25519 signing, the `.meta` sidecar, key rotation | -| [Colony spec](docs/colony-spec.md) | Full `colony.json` manifest reference | -| [Contributing](CONTRIBUTING.md) | How to add your app + how to contribute to Colony itself | +Full documentation is in [docs/](docs/). Start at +[docs/README.md](docs/README.md). ## Code signing policy -Free code signing provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org). +Free code signing provided by [SignPath.io](https://signpath.io), certificate by +[SignPath Foundation](https://signpath.org). + +Windows builds will be Authenticode-signed once SignPath Foundation has accepted +the project; until then they ship without Authenticode. Every release asset, on +every platform, is signed by the Project Colony organisation in CI, never on a +developer machine. Next to each asset on the release page: + +| File | What it is | +|---|---| +| `.sig` | an ed25519 signature over the asset, made with the organisation's release key | +| `.meta` | three lines binding the asset to its file name, its sha256 and the release version | +| `.meta.sig` | an ed25519 signature over the `.meta` | -Windows builds are Authenticode-signed once the SignPath Foundation has accepted the project; until then they ship without Authenticode. Every release asset, on every platform, is always signed with the organisation's ed25519 key, and Colony verifies that signature before it applies a self-update (see [Release signing](docs/release-signing.md)). +The private key is an organisation secret, used only by the shared +[sign-and-publish workflow](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/.github/workflows/sign-and-publish.yml) +in a job that builds nothing; the jobs that compile Colony never see it. Colony +checks all three files before it applies its own update, and refuses a release +older than the one running. To check a download yourself with OpenSSL 3 (the +same commands work for every asset): + +```bash +cat > colony-release.pub <<'EOF' +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEARNjg3Nn8H6/aBg1unwGjkUTcrdTxERNefVaqU8cFu0s= +-----END PUBLIC KEY----- +EOF +a=colony-linux +openssl pkeyutl -verify -pubin -inkey colony-release.pub -rawin -in "$a" -sigfile "$a.sig" +openssl pkeyutl -verify -pubin -inkey colony-release.pub -rawin -in "$a.meta" -sigfile "$a.meta.sig" +cat "$a.meta" # version=, asset=, sha256= +sha256sum "$a" # the digest must equal the sha256 line +``` + +How releases are built, signed and published: +[design/releases.md](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/design/releases.md#5-signing), +and for Colony's side of it, [docs/internals/release-signing.md](docs/internals/release-signing.md). Team roles and members: @@ -155,20 +262,69 @@ Team roles and members: ### Privacy policy -Colony talks to GitHub and to no other server. It has no telemetry, no analytics and no crash reporting. - -- **At every start, automatically**, and again when you press Refresh: it reads the Project-Colony catalogue from the GitHub API (`api.github.com`): the organisation's repository list and, for each repository, its `colony.json`, README, licence, changelog and icon, plus its latest release when the manifest does not list its files. -- **Update checks, automatically unless turned off** (Settings, "Check automatically", on by default) or when you press "Check for updates": it asks the GitHub API for the release of every installed app and the latest release of Colony itself. These requests name the repositories of the apps you have installed, so GitHub can see which Colony apps you use. -- **When you press "What's new in {version}"** on an app's page: it asks the GitHub API for that release, to show its notes. -- **When you install or update an app, or update Colony**: it looks the release up through the GitHub API, then downloads the release file and its `.sig`, `.meta` and `.meta.sig` from `github.com`, which serves them from GitHub's own download hosts. -- **When you sign in with GitHub** (optional): it uses GitHub's device flow (`github.com/login/device/code` and `github.com/login/oauth/access_token`, scope `read:user`), opens the verification page in your browser, and reads your user name from `api.github.com/user`. While you are signed in, your GitHub token is sent with the requests above, to GitHub only, to raise the API rate limit. - -Requests carry a `Colony-Launcher` user agent. Beyond that, they tell GitHub only which repositories and files are asked for (a download's file name shows your platform) and, while you are signed in, which account is asking; nothing from your settings or your files is sent. GitHub also sees what any web request shows it, such as your IP address, under the [GitHub General Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement). Links you click open in your web browser. The apps you install through Colony are separate programs with their own behaviour. +Colony talks to GitHub and to no other server. It has no telemetry, no analytics +and no crash reporting. + +- **At every start, automatically**, and again when you press Refresh: it reads + the Project-Colony catalogue from the GitHub API (`api.github.com`): the + organisation's repository list and, for each repository, its `colony.json`, + README, licence, changelog and icon, plus its latest release when the manifest + does not list its files. +- **Update checks, automatically unless turned off** (Preferences, General, + "Check automatically", on by default) or when you press "Check for updates": + it asks the GitHub API for the release of every installed app and the latest + release of Colony itself. These requests name the repositories of the apps you + have installed, so GitHub can see which Colony apps you use. +- **When you press "What's new in {version}"** on an app's page: it asks the + GitHub API for that release, to show its notes. +- **When you install or update an app, or update Colony**: it looks the release + up through the GitHub API, then downloads the release file and its `.sig`, + `.meta` and `.meta.sig` from `github.com`, which serves them from GitHub's own + download hosts. +- **When you sign in with GitHub** (optional): it uses GitHub's device flow + (`github.com/login/device/code` and `github.com/login/oauth/access_token`, + scope `read:user`), opens the verification page in your browser, and reads + your user name from `api.github.com/user`. While you are signed in, your + GitHub token is sent with the requests above, to GitHub only, to raise the API + rate limit. + +Requests carry a `Colony-Launcher` user agent. Beyond that, they tell GitHub only +which repositories and files are asked for (a download's file name shows your +platform) and, while you are signed in, which account is asking; nothing from +your settings or your files is sent. GitHub also sees what any web request shows +it, such as your IP address, under the +[GitHub General Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement). +Links you click open in your web browser. The apps you install through Colony are +separate programs with their own behaviour. + +What Colony keeps on your machine, and nowhere else (Linux paths shown): + +| Data | Where | +|---|---| +| Preferences and favourites | `~/.config/Colony/Colony/preferences/` | +| GitHub token, while you are signed in | the operating system's credential store: the macOS Keychain, the Windows Credential Manager, or the Secret Service on Linux (GNOME Keyring, KWallet). Only when that store is unavailable or does not give the token back, `~/.config/Colony/Colony/auth/github_token.json`, readable by your user alone (mode 600) | +| Catalogue, READMEs, icons, HTTP and scan caches, update downloads | `~/.cache/Colony/Colony/` | +| Diagnostics log, rewritten at every start | `~/.local/share/Colony/Colony/colony.log` | +| Installed apps, with their version and signature markers | `~/.local/share/Colony/apps//` | +| Menu entries for installed apps (Linux only) | `~/.local/share/applications/colony-.desktop` | + +On Windows the same files live under `%LOCALAPPDATA%\Colony\`, and on macOS +under `~/Library/Application Support/Colony/` and `~/Library/Caches/Colony/`. +Signing out deletes the token from the credential store and the file. A +self-update replaces the Colony executable where it is installed and keeps the +previous one next to it with an `.old` extension. ## License [GPL-3.0-or-later](LICENSE) © 2026 Project Colony contributors -Colony is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +Colony is free software: you can redistribute it and/or modify it under the terms +of the GNU General Public License as published by the Free Software Foundation, +either version 3 of the License, or (at your option) any later version. -The fonts Colony embeds come from the [colony-ui](https://crates.io/crates/colony-ui) crate and keep their own license, the SIL Open Font License 1.1, with the full text next to each font in colony-ui: [JetBrains Mono Nerd Font](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/JetBrainsMonoNerdFont/OFL.txt), [Font Awesome Free](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/FontAwesome/OFL.txt) and [OpenDyslexic](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/OpenDyslexic/OFL.txt). +The fonts Colony embeds come from the [colony-ui](https://crates.io/crates/colony-ui) +crate and keep their own license, the SIL Open Font License 1.1, with the full +text next to each font in colony-ui: +[JetBrains Mono Nerd Font](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/JetBrainsMonoNerdFont/OFL.txt), +[Font Awesome Free](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/FontAwesome/OFL.txt) +and [OpenDyslexic](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/crates/colony-ui/assets/fonts/OpenDyslexic/OFL.txt). diff --git a/assets/brand/png/colony-logo-512.png b/assets/brand/png/colony-logo-512.png new file mode 100644 index 0000000..0fce7cd Binary files /dev/null and b/assets/brand/png/colony-logo-512.png differ diff --git a/assets/brand/png/colony-logo-light-1024.png b/assets/brand/png/colony-logo-light-1024.png new file mode 100644 index 0000000..7f03ec0 Binary files /dev/null and b/assets/brand/png/colony-logo-light-1024.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9e8f509 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,25 @@ +# Colony documentation + +Sorted by who is reading, not by subject. + +## Using Colony + +| | | +|---|---| +| [guide/tutorial.md](guide/tutorial.md) | getting Colony on your machine, then a first app installed, launched and updated | +| [guide/faq.md](guide/faq.md) | where Colony keeps its files, how sign-in and updates work, and what to do when something looks wrong | +| [guide/colony-spec.md](guide/colony-spec.md) | for app authors: the `colony.json` manifest, asset names, signatures, and how to check a manifest before a release | + +## Reading the code + +| | | +|---|---| +| [internals/architecture.md](internals/architecture.md) | where everything lives in `src/`, how a message flows, and what is cached where | +| [internals/release-signing.md](internals/release-signing.md) | how releases are signed, what Colony checks before it installs or updates, and how the key is rotated | +| [../CONTRIBUTING.md](../CONTRIBUTING.md) | building, testing, the checks a pull request must pass, and adding an app to the catalog | + +## Why it exists + +| | | +|---|---| +| [../README.md#why-colony](../README.md#why-colony) | what Colony replaces, and what it does that downloading each release by hand does not | diff --git a/docs/docs.md b/docs/docs.md deleted file mode 100644 index 6ced0c8..0000000 --- a/docs/docs.md +++ /dev/null @@ -1,19 +0,0 @@ -# Colony Documentation - -This folder contains the technical and end-user documentation for Colony. - -## Contents - -| File | Audience | Purpose | -|----------------------------------------|-----------------|-----------------------------------------------------------| -| [tutorial.md](tutorial.md) | End users | Step-by-step walkthrough: install, interface, first app | -| [faq.md](faq.md) | End users | Common questions + troubleshooting | -| [architecture.md](architecture.md) | Contributors | Internal architecture, tech stack, file structure, data flow | -| [colony-spec.md](colony-spec.md) | App developers | `colony.json` manifest spec + asset naming convention | -| [release-signing.md](release-signing.md) | Maintainers | ed25519 release signing, the signed `.meta` sidecar, key rotation | - -## See also - -- [Project-Colony GitHub organization](https://github.com/Project-Colony) -- [Contributing guide](../CONTRIBUTING.md) — adding your app + contributing to Colony itself -- [Release workflow template](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/templates/sign-and-publish-caller.yml) and the shared signing workflow it calls, in Project-Colony-Resources diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 3f8717e..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,213 +0,0 @@ -# Colony — FAQ - -## General - -### What is Colony exactly? - -A desktop application launcher. It curates a catalog of small Rust-native apps (the Project-Colony ecosystem), downloads them from GitHub Releases on demand, and launches them. It also scans your system for apps you already have installed and surfaces them in the same grid. - -### Is Colony free / open source? - -Yes. [GPL-3.0-or-later licensed](../LICENSE), no telemetry, no mandatory account, no paid tier. Source at [Project-Colony/Colony](https://github.com/Project-Colony/Colony). - -### What platforms are supported? - -Linux x86_64, Windows x86_64, macOS ARM (Apple Silicon), macOS x86_64 (Intel). ARM Linux and BSDs aren't built yet — build-from-source should work but is untested. - -### Why another launcher? - -Two reasons: (1) centralize the ecosystem of small Rust-native Project-Colony apps into one install-and-forget entry point, (2) distribute those apps without forcing each one through package managers, flatpak, or the Microsoft Store. The full pitch is in the [README](../README.md#design-principles). - ---- - -## Installation & updates - -### How do I install Colony on Arch / Manjaro / EndeavourOS? - -``` -paru -S colony-bin # prebuilt, recommended -``` - -For the bleeding edge (built from HEAD every upgrade): - -``` -paru -S colony-git -``` - -Both provide `colony` and add a `.desktop` entry so rofi/wofi/GNOME/KDE launchers find it. - -### I'm on Debian/Fedora/other — what do I do? - -Download `colony-linux` from the [latest release](https://github.com/Project-Colony/Colony/releases/latest), `chmod +x`, run. Details in the [tutorial](tutorial.md#other-linux-distributions). - -### Does Colony update itself? - -Yes. Colony periodically checks the `Project-Colony/Colony` repo for a newer release. When available, a badge appears in the sidebar; clicking it downloads the new binary, backs up the old one (`.old`), swaps them, and respawns. If the swap fails the backup is restored automatically. - -On Arch via `paru -S colony-bin`, `paru -Syu` handles the upgrade — Colony's in-app self-update and the AUR package stay in sync because the AUR package auto-bumps from every Colony GitHub Release. - -### How do apps in the catalog update? - -Colony compares each installed app's stored version (under `~/.local/share/Colony/apps//.colony_version`) against the latest GitHub Release tag. When newer, the detail view shows an **Update** button. - -### How do I uninstall an app I installed through Colony? - -Open the app in Colony → detail view → **Uninstall**. Or delete the app's directory under `~/.local/share/Colony/apps//` manually. - -### How do I uninstall Colony itself? - -AUR: `sudo pacman -R colony-bin` (or `colony-git`). Manual install: delete the binary plus `~/.config/Colony/`, `~/.cache/Colony/`, and `~/.local/share/Colony/`. - ---- - -## GitHub authentication - -### Do I need a GitHub account? - -No, Colony works without one. The cost is a 60 request/hour rate limit (the public GitHub API default). If you browse actively you'll hit it fast. - -### Connecting changes what? - -You get a 5000 req/h limit and Colony can read private repos you have access to (useful for testing an unpublished app). Only public-repo read scopes are requested. - -### Where is the token stored? - -In your OS keychain (GNOME keyring, KWallet, macOS Keychain, Windows Credential Manager) via the [keyring](https://crates.io/crates/keyring) crate. If no keychain is available (headless Linux, CI) it falls back to `~/.config/Colony/Colony/auth/github_token.json` with `chmod 600`. - -### How do I disconnect? - -Settings → GitHub → **Disconnect**. The token is deleted locally. The app lease on your GitHub account is not revoked server-side — you can revoke it from [GitHub settings → Applications](https://github.com/settings/applications) if you want. - ---- - -## Catalog / detection - -### My app isn't showing up — why? - -Most common causes: - -1. No `colony.json` at the root of the repo. -2. `colony.json` has an invalid `category` (valid: `Development`, `Graphics`, `Network`, `Office`, `Multimedia`, `System`, `Utilities`, `Security`, `Games`, `Other`). -3. No GitHub release published (drafts don't count). -4. Release assets don't match the `-` convention (see [colony-spec.md](colony-spec.md)). -5. The repo is not under the `Project-Colony` org (Colony only scans that org). -6. GitHub rate limit hit — connect your account. - -Cache file to inspect: `~/.cache/Colony/Colony/repos_cache.json`. - -### Does Colony support private repos? - -Yes, if you authenticate with an account that has access. The app cards and downloads go through your token. - -### Can I add apps from outside Project-Colony? - -Not automatically. The catalog is scoped to `Project-Colony/*`. If you want to add your own, ask a maintainer to fork your repo under the org (or transfer it). - -### Why don't I see my system apps (installed via pacman / MSI / .app)? - -Colony scans these paths: - -- **Linux**: `~/.local/share/applications`, `/usr/share/applications`, flatpak user/system applications. -- **Windows**: Start Menu. -- **macOS**: `/Applications`, `~/Applications`. - -If your app is installed but not in any of these (portable binary without `.desktop`, for example) Colony can't find it. Ask your distro package or create a `.desktop` file. - ---- - -## Configuration / data - -### Where does Colony store its stuff? - -| What | Path (Linux) | -|----------------------------|----------------------------------------------------------| -| Preferences | `~/.config/Colony/Colony/preferences/preferences.json` | -| Favorites | `~/.config/Colony/Colony/preferences/favorites.json` | -| GitHub token (fallback) | `~/.config/Colony/Colony/auth/github_token.json` (`600`) | -| Installed app binaries | `~/.local/share/Colony/apps//` | -| Installed version marker | `~/.local/share/Colony/apps//.colony_version` | -| Resolved asset filename | `~/.local/share/Colony/apps//.colony_asset` | -| Repo list cache | `~/.cache/Colony/Colony/repos_cache.json` | -| Manifest docs cache | `~/.cache/Colony/Colony/repo-docs//` | -| App icon cache | `~/.cache/Colony/Colony/repo-icons//icon.png` | -| System app scan cache | `~/.cache/Colony/Colony/scan_cache.json` | -| Self-update staging | `~/.cache/Colony/Colony/update-staging/` | -| HTTP ETag cache | `~/.cache/Colony/Colony/http_etags.json` | -| Diagnostics log | `~/.local/share/Colony/Colony/colony.log` | - -On Windows all three roots are `%LOCALAPPDATA%\Colony\`, and on macOS config -and data share `~/Library/Application Support/Colony/` while the cache sits in -`~/Library/Caches/Colony/`. The layout is defined once in -[Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/design/filesystem.md). - -Purging the `~/.cache/Colony/` directory forces a full re-scan and re-fetch. - -### Can I edit `preferences.json` by hand? - -Yes. Colony re-reads it on launch. The file is JSON with field names matching what the Settings UI displays. Back it up first — malformed JSON resets to defaults. - -### Can I override the scan directories? - -Yes. Edit `~/.config/Colony/Colony/preferences/preferences.json` and add custom paths under the platform-appropriate scan settings. The Settings UI also exposes this for Linux. - -### I changed a theme and don't see it — why? - -Themes apply immediately, no restart. If a palette looks off, verify Preferences → Appearance → Theme has the expected family + palette selected. All 59 palettes are compiled into the binary, so if it's missing you're on an old version: update. - ---- - - -## Can I rename or reorder the sidebar sections? - -Yes. Colony looks for a `categories.json` override before falling back to the -copy compiled into the binary, in this order: - -1. `~/.config/colony/categories.json` on Linux, - `~/Library/Application Support/colony/categories.json` on macOS, and - `%LOCALAPPDATA%\colony\categories.json` on Windows, where - `%APPDATA%\colony\` is still read after it -2. next to the `colony` binary -3. `./config/categories.json` - -Copy the shipped [`config/categories.json`](https://github.com/Project-Colony/Colony/blob/main/config/categories.json) -to the first path and edit it. Each entry takes `name`, `icon` (a Nerd Font -glyph), `origin` (`colony`, `windows`, `external`), `category`, and an optional -`platforms` list that hides the section on other operating systems. A Favorites -section is injected automatically if you leave it out. - -The same search order applies to `colony.toml`, which controls the directories -scanned for locally installed apps. - -## Troubleshooting - -### Colony won't launch / crashes immediately - -Run from a terminal to see stderr: - -```bash -colony 2>&1 | tee colony.log -``` - -Common causes: - -- A missing D-Bus session (the keyring backend needs one; a bare TTY or a minimal container has none) — on AUR the deps are pulled automatically. For manual downloads, install them via your package manager. -- Corrupted cache — `rm -rf ~/.cache/Colony` and relaunch. -- Corrupted preferences — `mv ~/.config/Colony/Colony/preferences/preferences.json{,.bak}` and relaunch to regenerate defaults. - -### Download stuck / very slow - -Colony uses a 300s timeout per download. If you're on slow network this may not be enough; interrupt with the Cancel button on the download toast. - -### Self-update fails - -The old binary is backed up as `.old` next to the running executable. If the swap fails, Colony restores it automatically. If for some reason you end up with a broken binary: - -```bash -mv ~/.local/bin/colony.old ~/.local/bin/colony # (adjust path to your install) -``` - -Or just re-download from the [latest release](https://github.com/Project-Colony/Colony/releases/latest). - -### Where can I get help? - -Open an issue on [Project-Colony/Colony](https://github.com/Project-Colony/Colony/issues) with your OS, Colony version (`colony --version`), and a log excerpt if possible. diff --git a/docs/colony-spec.md b/docs/guide/colony-spec.md similarity index 82% rename from docs/colony-spec.md rename to docs/guide/colony-spec.md index 41697bd..32593af 100644 --- a/docs/colony-spec.md +++ b/docs/guide/colony-spec.md @@ -1,4 +1,4 @@ -# Specification — Colony App Discovery via GitHub +# colony.json specification: how Colony discovers apps on GitHub ## Context @@ -7,7 +7,7 @@ Colony is an application launcher that downloads, updates, and launches software ## Official source - **Official GitHub organization:** `Project-Colony` (https://github.com/Project-Colony) -- Colony automatically scans **all GitHub repositories** in the Project-Colony organization to detect compatible software. +- Colony automatically scans **every public repository** of the Project-Colony organization to detect compatible software. ## Detecting a Colony app @@ -24,8 +24,9 @@ The manifest serves to: ```json { + "$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json", "name": "orCAL", - "category": "Utilities" + "category": "utility" } ``` @@ -44,8 +45,9 @@ This is the **recommended format** for new Colony apps using the standard releas ```json { + "$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json", "name": "Lilypad", - "category": "Security", + "category": "security", "platforms": ["windows", "linux", "macos"], "releaseFiles": { "windows": { @@ -72,8 +74,9 @@ This is the **recommended format** for new Colony apps using the standard releas ```json { + "$schema": "https://raw.githubusercontent.com/Project-Colony/Project-Colony-Resources/main/generated/colony.schema.json", "name": "Lilypad", - "category": "Security", + "category": "security", "platforms": ["windows", "linux", "macos"], "releaseFiles": { "windows": { @@ -110,7 +113,7 @@ Signature and checksum siblings (`.sig`, `.sha256`, `.yml`, ...) are never match | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | yes | Display name in Colony. Should match the repository name. | -| `category` | `string` | yes | Colony category, as shown in the sidebar (case-insensitive). Values: `Development`, `Graphics`, `Network`, `Office`, `Multimedia`, `System`, `Utility` (alias `Utilities`), `Security`, `Game` (alias `Games`), `Other`. Unknown values fall back to `Other`. | +| `category` | `string` | yes | Colony category, which picks the sidebar section (case-insensitive). Canonical values: `development`, `graphics`, `network`, `office`, `multimedia`, `system`, `utility`, `security`, `game`, `other`; `utilities` and `games` are accepted too. Unknown values fall back to `other`, and `colony validate-manifest` reports them. | | `platforms` | `string[]` | no | Supported platforms. Values: `"windows"`, `"linux"`, `"macos"` (Apple Silicon), `"macos-x86"` (Intel). Auto-detected if omitted. | | `releaseFiles` | `object` | no | Map of platform → release entry. Each key is a platform. Auto-detected if omitted. | | `icon` | `string` | no | Path (relative to the repo root) to a square **PNG** app icon shown in the Colony grid. See [App icon](#app-icon). | @@ -161,13 +164,13 @@ Colony extracts information from the repository to enrich the display: Colony shows a per-app icon in the grid. It is resolved, in order: -1. The `icon` manifest field — a path relative to the repo root (e.g. `"assets/icons/icon.png"`), fetched from the repo like the README. +1. The `icon` manifest field: a path relative to the repo root (e.g. `"assets/icons/icon.png"`), fetched from the repo like the README. 2. If `icon` is absent, a conventional **`icon.png`** at the repo root. 3. If neither exists, Colony draws a generated category tile (a tinted hexagon with the category glyph). The icon is fetched once and cached on disk (`/repo-icons//icon.png`), decoded to an image, and rendered at a fixed ~54 px tile. -**Format**: a square **PNG** (transparent or opaque). PNG is the only format Colony decodes — not `.ico` / `.svg` / `.icns` (those are for a platform's *own* application icon, which is separate from the Colony grid icon). A 48–128 px source works well; pixel-art icons should be authored at their native size. +**Format**: a square **PNG** (transparent or opaque). PNG is the only format Colony decodes, not `.ico` / `.svg` / `.icns` (those are for a platform's *own* application icon, which is separate from the Colony grid icon). A 48 to 128 px source works well; pixel-art icons should be authored at their native size. ### Standard icon layout @@ -175,30 +178,30 @@ Project-Colony repos keep every icon format in one uniform folder, so tooling (r ``` assets/icons/ -├── icon.png — canonical square PNG; the Colony grid icon (declared via `icon`) -├── icon.ico — Windows application icon (multi-size container, 16–256 px) -└── icon.icns — macOS application icon (multi-size container) +├── icon.png canonical square PNG; the Colony grid icon (declared via `icon`) +├── icon.ico Windows application icon (multi-size container, 16 to 256 px) +└── icon.icns macOS application icon (multi-size container) ``` ```json { "icon": "assets/icons/icon.png" } ``` -Declaring the path explicitly is preferred over relying on the root-`icon.png` convention: it documents the layout and spares Colony a probing request. The `.ico` / `.icns` files are not read by Colony — they are staged there for each app's own platform packaging (executable icon, taskbar, dock). +Declaring the path explicitly is preferred over relying on the root-`icon.png` convention: it documents the layout and spares Colony a probing request. The `.ico` / `.icns` files are not read by Colony: they are staged there for each app's own platform packaging (executable icon, taskbar, dock). ## GitHub OAuth authentication -Colony offers a **"Connect"** button that initiates a GitHub Device Flow OAuth. +Colony offers a **Sign in with GitHub** button, in the panel the sidebar's **GitHub** button opens, that starts a GitHub Device Flow OAuth. ### Flow - **Device Flow**: user receives a code, opens a URL in their browser, enters the code to authorize Colony. -- Minimal scopes (public repos need no scope; auth increases rate limits to 5000 req/h). +- Scope `read:user` only: enough to read the user name. Public repositories need no scope, and signing in only raises the rate limit to 5000 requests per hour. ### Token management -- Stored in the OS keychain (keyring crate), with a file fallback (chmod 600). -- Revocation via disconnect button (local deletion). +- Stored in the OS credential store (keyring-core with the macOS Keychain, Windows Credential Manager or Secret Service store), with a file fallback (chmod 600). +- **Sign out** deletes it locally; the authorisation itself is revoked from the user's GitHub settings. - Authenticated calls use: `Authorization: Bearer `. ### Unauthenticated mode @@ -211,8 +214,8 @@ Colony offers a **"Connect"** button that initiates a GitHub Device Flow OAuth. When a release publishes a detached signature named `.sig` next to an asset, Colony **requires** it to verify: the raw 64-byte ed25519 signature over the asset bytes, made with the Project-Colony release key (the same key that -signs the launcher itself - see `docs/release-signing.md` and the embedded -public key in `src/signing.rs`). An invalid signature aborts the install. +signs the launcher itself: see [release-signing.md](../internals/release-signing.md) and the embedded +public key in [`src/signing.rs`](../../src/signing.rs)). An invalid signature aborts the install. An asset without a `.sig` sibling installs as before (legacy unsigned app) - signing is adopted per-app, no flag day - **unless** the manifest declares: @@ -247,8 +250,8 @@ org-signed artefact under a new tag. Sidecars are **opportunistic and pinned**: an app that publishes none still installs, but once Colony has verified one for an app, a later release that stops publishing them is refused. So the ecosystem can adopt them repo by repo -with no flag day, and no repo can quietly stop. The release template ships the -`sign` job that emits them. +with no flag day, and no repo can quietly stop. The release template's +`sign-and-publish` job, which calls the shared signing workflow, emits them. ## Validating your manifest diff --git a/docs/guide/faq.md b/docs/guide/faq.md new file mode 100644 index 0000000..dec8474 --- /dev/null +++ b/docs/guide/faq.md @@ -0,0 +1,211 @@ +# Colony FAQ + +## General + +### What is Colony exactly? + +A desktop application launcher. It curates a catalog of small Rust-native apps (the Project-Colony ecosystem), downloads them from GitHub Releases on demand, and launches them. It also scans your system for apps you already have installed and shows them in the same grid. + +### Is Colony free / open source? + +Yes. [GPL-3.0-or-later licensed](../../LICENSE), no telemetry, no mandatory account, no paid tier. Source at [Project-Colony/Colony](https://github.com/Project-Colony/Colony). + +### What platforms are supported? + +Linux x86_64, Windows x86_64, macOS ARM (Apple Silicon), macOS x86_64 (Intel). ARM Linux and the BSDs are not built yet: building from source should work but is untested. + +### Why another launcher? + +Two reasons: (1) gather the small Rust-native Project-Colony apps behind one install-and-forget entry point, (2) distribute those apps without forcing each one through package managers, Flatpak, or the Microsoft Store. The full pitch is in the [README](../../README.md#why-colony). + +--- + +## Installation & updates + +### How do I install Colony on Arch / Manjaro / EndeavourOS? + +``` +paru -S colony-bin # the released binary, recommended +``` + +For the latest commit (rebuilt when the repository moves): + +``` +paru -S colony-git +``` + +Both provide `colony` and add a `.desktop` entry so rofi, wofi, GNOME and KDE launchers find it. + +### I'm on Debian/Fedora/other: what do I do? + +Download `colony-linux` from the [latest release](https://github.com/Project-Colony/Colony/releases/latest), `chmod +x`, run. Details in the [tutorial](tutorial.md#other-linux-distributions). + +### Does Colony update itself? + +Yes. At every start (unless "Check automatically" is off in Preferences, General) and whenever you press "Check for updates", Colony asks the `Project-Colony/Colony` repository for a newer release. When there is one, a badge appears in the sidebar; clicking it downloads the new binary and checks its signature and signed `.meta`. Restarting then backs up the old binary (`.old`), swaps in the new one, and starts it. If the swap fails, the backup is restored automatically. + +On Arch, `paru -Syu` handles the upgrade instead: an install pacman manages cannot replace its own binary, so Colony shows guidance rather than an update button there. The AUR package is bumped at every Colony release. + +### How do apps in the catalog update? + +Colony compares each installed app's stored version (in `~/.local/share/Colony/apps//.colony_version`) with its latest GitHub release tag. When the release is newer, the detail view shows an **Update** button. + +### How do I uninstall an app I installed through Colony? + +Open the app in Colony, then use the uninstall action in its detail view and confirm. Or delete the app's directory under `~/.local/share/Colony/apps//` by hand (on Linux, also its menu entry, `~/.local/share/applications/colony-.desktop`). + +### How do I uninstall Colony itself? + +Sign out of GitHub first if you signed in, so the token leaves your credential store. Then, on the AUR: `sudo pacman -R colony-bin` (or `colony-git`). For a manual install, delete the binary. To remove everything Colony wrote, also delete `~/.config/Colony/Colony/`, `~/.cache/Colony/Colony/`, `~/.local/share/Colony/` and the `colony-*.desktop` files in `~/.local/share/applications/`. (`~/.local/share/Colony/apps/` holds the apps you installed, so delete it only if you want those gone too.) + +--- + +## GitHub authentication + +### Do I need a GitHub account? + +No, Colony works without one. The cost is a rate limit of 60 requests per hour (the public GitHub API default). If you browse actively you will hit it fast. + +### Connecting changes what? + +You get a limit of 5000 requests per hour. Nothing else: Colony requests the `read:user` scope only, which lets it read your user name. It gets no access to your repositories, and the catalog it shows is the organisation's public repositories whether you are signed in or not. + +### Where is the token stored? + +In your operating system's credential store: the macOS Keychain, the Windows Credential Manager, or the Secret Service on Linux (GNOME Keyring, KWallet), through [keyring-core](https://crates.io/crates/keyring-core) and the native store of each OS. If no store is available (a headless Linux session, CI), it falls back to `~/.config/Colony/Colony/auth/github_token.json`, readable by your user alone (mode 600). That file is removed only once the token reads back from the credential store. + +### How do I disconnect? + +Click **GitHub** in the sidebar, then **Sign out**. The token is deleted from your machine. The authorisation on your GitHub account is not revoked server-side: revoke it from [GitHub settings, Applications](https://github.com/settings/applications) if you want. + +--- + +## Catalog / detection + +### My app isn't showing up: why? + +Most common causes: + +1. No `colony.json` at the root of the repository. +2. `colony.json` has an unknown `category`, which files the app under Other. Valid values: `development`, `graphics`, `network`, `office`, `multimedia`, `system`, `utility`, `security`, `game`, `other` (case-insensitive; `utilities` and `games` are accepted too). +3. No GitHub release published (drafts do not count). +4. Release assets do not match the `-` convention (see [colony-spec.md](colony-spec.md)). `colony validate-manifest` checks both of the above before you release. +5. The repository is not a public repository of the `Project-Colony` organisation (Colony only lists those). +6. GitHub rate limit hit: sign in with GitHub. + +Cache file to inspect: `~/.cache/Colony/Colony/repos_cache.json`. + +### Does Colony support private repos? + +No. Colony lists the public repositories of the `Project-Colony` organisation, and the token it asks for (`read:user`) gives it no access to private ones. + +### Can I add apps from outside Project-Colony? + +Not automatically. The catalog is scoped to `Project-Colony/*`. If you want to add your own, ask a maintainer to fork your repository under the organisation (or transfer it). + +### Why don't I see my system apps (installed via pacman / MSI / .app)? + +Colony scans these places: + +- **Linux**: `~/.local/share/applications` (or `$XDG_DATA_HOME/applications`), the `applications` folder of every `$XDG_DATA_DIRS` entry (by default `/usr/share/applications` and `/usr/local/share/applications`), and the Flatpak and Snap export folders. +- **Windows**: the Start Menu, for all users and for you. +- **macOS**: `/Applications`, `/System/Applications`, `~/Applications`. + +If your app is installed but not in any of these (a portable binary without a `.desktop` file, for example), Colony cannot find it. Create a `.desktop` file for it, or add its folder to the scanned directories (see below). + +--- + +## Configuration / data + +### Where does Colony store its stuff? + +| What | Path (Linux) | +|---|---| +| Preferences | `~/.config/Colony/Colony/preferences/preferences.json` | +| Favorites | `~/.config/Colony/Colony/preferences/favorites.json` | +| GitHub token (fallback only) | `~/.config/Colony/Colony/auth/github_token.json` (mode 600) | +| Installed app binaries | `~/.local/share/Colony/apps//` | +| Installed version marker | `~/.local/share/Colony/apps//.colony_version` | +| Resolved asset filename | `~/.local/share/Colony/apps//.colony_asset` | +| Signature markers | `~/.local/share/Colony/apps//.colony_signed` and `.colony_meta` | +| Menu entries for installed apps | `~/.local/share/applications/colony-.desktop` | +| Repo list cache | `~/.cache/Colony/Colony/repos_cache.json` | +| Manifest docs cache | `~/.cache/Colony/Colony/repo-docs//` | +| App icon cache | `~/.cache/Colony/Colony/repo-icons//icon.png` | +| System app scan cache | `~/.cache/Colony/Colony/scan_cache.json` | +| Self-update staging | `~/.cache/Colony/Colony/update-staging/` | +| HTTP ETag cache | `~/.cache/Colony/Colony/http_etags.json` | +| Diagnostics log | `~/.local/share/Colony/Colony/colony.log` | + +On Windows all three roots are `%LOCALAPPDATA%\Colony\`, and on macOS config +and data share `~/Library/Application Support/Colony/` while the cache sits in +`~/Library/Caches/Colony/`. Menu entries are written on Linux only. The layout +is defined once in +[Project-Colony-Resources](https://github.com/Project-Colony/Project-Colony-Resources/blob/main/design/filesystem.md). + +Deleting `~/.cache/Colony/Colony/` forces a full re-scan and re-fetch, and loses nothing else. + +### Can I edit `preferences.json` by hand? + +Yes, while Colony is closed: it reads the file at launch and rewrites it when you change a preference. The fields are named after what they hold (`selected_theme`, `font_size` and so on). Back it up first: a file Colony cannot parse is moved aside to `preferences.corrupt`, and Colony starts from defaults. + +### Can I override the scan directories? + +Yes, with a `colony.toml`. Copy the shipped [`config/colony.toml`](https://github.com/Project-Colony/Colony/blob/main/config/colony.toml) to `~/.config/colony/colony.toml` (on macOS `~/Library/Application Support/colony/`, on Windows `%LOCALAPPDATA%\colony\`) and edit its lists: `[scan] unix = [...]` on Linux and macOS, `windows = [...]` on Windows. Environment variables such as `${HOME}` are expanded. The list replaces the defaults; it does not add to them. Colony looks for the file in the same places, and in the same order, as `categories.json` below. + +### I changed a theme and don't see it: why? + +Themes apply immediately, no restart. If a palette looks off, check that Preferences, Appearance has the expected family and palette selected. All 59 palettes are compiled into the binary, so if one is missing you are on an old version: update. + +### Can I rename or reorder the sidebar sections? + +Yes. Colony looks for a `categories.json` override before falling back to the +copy compiled into the binary, in this order: + +1. `~/.config/colony/categories.json` on Linux, + `~/Library/Application Support/colony/categories.json` on macOS, and + `%LOCALAPPDATA%\colony\categories.json` on Windows, where + `%APPDATA%\colony\` is still read after it +2. `config/categories.json` next to the `colony` binary +3. `./config/categories.json` + +Copy the shipped [`config/categories.json`](https://github.com/Project-Colony/Colony/blob/main/config/categories.json) +to the first path and edit it. Each entry takes `name`, `icon` (a Nerd Font +glyph), `origin` (`colony`, `windows`, `external`), `category`, and an optional +`platforms` list that hides the section on other operating systems. A Favorites +section is injected automatically if you leave it out. + +--- + +## Troubleshooting + +### Colony won't launch / crashes immediately + +Run it from a terminal to see what it prints: + +```bash +colony 2>&1 | tee colony-console.log +``` + +The same lines are in `~/.local/share/Colony/Colony/colony.log`. Things worth trying: + +- Clear the cache: `rm -rf ~/.cache/Colony/Colony` and relaunch. It only holds what Colony can fetch again. +- Start from default preferences: `mv ~/.config/Colony/Colony/preferences/preferences.json{,.bak}` and relaunch. + +### Download stuck / very slow + +Colony gives up on a download only when no data has arrived for 60 seconds, so a slow line still finishes a large file. To stop one, press the × on its progress bar. + +### Self-update fails + +The old binary is kept as `.old` next to the running executable. If the swap fails, Colony restores it automatically. If you still end up with a broken binary: + +```bash +mv ~/.local/bin/colony.old ~/.local/bin/colony # adjust the path to your install +``` + +Or download it again from the [latest release](https://github.com/Project-Colony/Colony/releases/latest). + +### Where can I get help? + +Open an issue on [Project-Colony/Colony](https://github.com/Project-Colony/Colony/issues) with your OS, your Colony version (`colony --version`), and a log excerpt if possible. diff --git a/docs/guide/tutorial.md b/docs/guide/tutorial.md new file mode 100644 index 0000000..a09faf0 --- /dev/null +++ b/docs/guide/tutorial.md @@ -0,0 +1,177 @@ +# Colony tutorial + +This is a step-by-step walkthrough for first-time Colony users. By the end of +it you will have Colony installed, know its interface, have installed your +first app from the catalog, and have set the theme and your preferences. + +## Prerequisites + +A Linux, Windows, or macOS desktop and about 50 MB of free disk space for Colony +itself, plus room for each Colony app you install (typically 20 to 100 MB). + +## 1. Install Colony + +Pick the path that matches your system. + +### Arch Linux + +```bash +paru -S colony-bin # the released binary, fastest +# or +paru -S colony-git # built from the latest commit +``` + +### Other Linux distributions + +Download `colony-linux` from the +[latest release](https://github.com/Project-Colony/Colony/releases/latest), +make it executable, and run it: + +```bash +curl -L -o colony https://github.com/Project-Colony/Colony/releases/latest/download/colony-linux +chmod +x colony +./colony +``` + +Optional: move it to `~/.local/bin/colony` to have it in your `$PATH`, and +create a matching `~/.local/share/applications/colony.desktop` so your desktop's +launcher picks it up (the AUR packages install this file for you). + +To check the download against its signature first, follow +[Code signing policy](../../README.md#code-signing-policy) in the README. + +### Windows + +Download `colony-windows.exe` from the +[latest release](https://github.com/Project-Colony/Colony/releases/latest) and +double-click it. Windows Defender may warn you, because the file is not +Authenticode-signed yet (it is signed with the Project Colony release key, which +Windows does not know): click "More info", then "Run anyway". + +### macOS + +Download `colony-macos` (Apple Silicon) or `colony-macos-x86` (Intel) from the +[latest release](https://github.com/Project-Colony/Colony/releases/latest). + +```bash +chmod +x colony-macos +xattr -d com.apple.quarantine colony-macos # remove the Gatekeeper quarantine +./colony-macos +``` + +## 2. First launch: the main interface + +When you open Colony for the first time, a short guided tour points at each +part of the window. It looks like this: + +``` +┌─────────────┬──────────────────────────────────────────┐ +│ Colony │ Search... │ +│ │ │ +│ All │ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ Favorites │ │ App 1 │ │ App 2 │ │ App 3 │ │ +│ Linux │ └────────┘ └────────┘ └────────┘ │ +│ Develop. │ │ +│ Graphics │ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ Network │ │ App 4 │ │ App 5 │ │ App 6 │ │ +│ ... │ └────────┘ └────────┘ └────────┘ │ +│ │ │ +│ GitHub │ │ +│ Rescan │ │ +└─────────────┴──────────────────────────────────────────┘ +``` + +- **Colony**: the name at the top of the sidebar opens Preferences: General, + Appearance, Accessibility, Storage, Shortcuts and About. Click it again, or + Close, to go back. +- **Sidebar sections**: **All** lists the whole Colony catalog, and + **Favorites** the apps you starred. The section named after your system + (**Linux**, **Windows** or **macOS**) lists the applications already + installed on it, next to the catalog apps that have a build for it. Below + come the Colony categories: Development, Graphics, Network, Office, + Multimedia, System, Utilities, Security, Games and Other. +- **App grid**: a card for every app in the selected section. Click a card to + open its detail view. +- **Search**: the bar above the grid filters by app name. +- **GitHub** and **Rescan**, at the bottom of the sidebar: sign in with GitHub + (see section 6), and scan your system for installed applications again. + +## 3. Install your first Colony app + +1. Pick a category in the sidebar (for example **Multimedia**). +2. Click the app card you want. +3. The detail view opens: + - the app's README, licence and changelog, fetched from its repository + - the installed version, if any, and the latest release + - a **Download** button (or **Update** and **Launch** once it is installed) +4. Click **Download**. Colony downloads the file for your platform (for + example `grape-linux`), checks its signature when the release has one and + its SHA-256 when the app's manifest gives one, and stores it under + `~/.local/share/Colony/apps//`. +5. When the download completes, the button becomes **Launch**. Click it: the + app starts as a separate process, independent of Colony. + +On Linux, Colony also writes a menu entry for the app +(`~/.local/share/applications/colony-.desktop`), so you can start it later +from your desktop's launcher (rofi, wofi, GNOME Activities, KDE) too. + +## 4. System apps, not just Colony apps + +Colony scans your system for applications that are already installed: + +- **Linux**: `.desktop` files in `~/.local/share/applications` (or + `$XDG_DATA_HOME/applications`), the `applications` folder of every + `$XDG_DATA_DIRS` entry (by default `/usr/share/applications` and + `/usr/local/share/applications`), and the Flatpak and Snap export folders. +- **Windows**: Start Menu entries, for all users and for you. +- **macOS**: `.app` bundles under `/Applications`, `/System/Applications` and + `~/Applications`. + +Open the section named after your system to see them. A scan runs at startup +unless you turn that off in Preferences, Storage; **Rescan** runs one on demand. + +## 5. Change the theme + +Click **Colony** at the top of the sidebar, then **Appearance**. Colony ships 26 +theme families and 59 palettes (Catppuccin, Gruvbox, Nord, Dracula, Rosé Pine, +Tokyo Night and more), plus an accent colour. Changes apply instantly, no +restart needed. **Accessibility**, next to it, has the text size, high +contrast, a dyslexia-friendly font and reduced motion. + +## 6. Connect your GitHub account (optional) + +Without an account Colony uses the public GitHub API, which allows 60 requests +per hour. If you browse a lot of apps, sign in for a limit of 5000 per hour: + +1. Click **GitHub** in the sidebar. +2. Click **Sign in with GitHub**. +3. Colony shows a code and opens GitHub's device page in your browser. Enter + the code there and authorise Colony. +4. Colony keeps the token in your operating system's credential store (the + macOS Keychain, the Windows Credential Manager, or the Secret Service on + Linux). Only when that store is unavailable does it fall back to a file that + only your user can read. +5. **Sign out**, in the same panel, deletes the token from your machine. + +Colony asks GitHub for the `read:user` scope only: enough to read your user +name. It gets no access to your repositories, private or public, and the catalog +it shows is the organisation's public repositories either way. + +## 7. Keep Colony up to date + +Colony looks for a new version of itself at every start (unless you turn off +"Check automatically" in Preferences, General) and whenever you press "Check for +updates". When one is available, a badge appears at the bottom of the sidebar: +click it to download the update, then restart. On an AUR install, `paru -Syu` +upgrades Colony with the rest of your system instead. + +## Troubleshooting + +- **App card not showing up?** Check that the repository has a `colony.json` at + its root and a published release with assets named `-`. See + [colony-spec.md](colony-spec.md) for the full manifest reference. +- **Rate limit hit?** Sign in with GitHub (section 6). +- **Download fails?** Check your network. Colony tries three times and resumes + where it stopped. The log, `~/.local/share/Colony/Colony/colony.log`, says + what went wrong. +- **More questions?** See the [FAQ](faq.md). diff --git a/docs/architecture.md b/docs/internals/architecture.md similarity index 90% rename from docs/architecture.md rename to docs/internals/architecture.md index 2a93b80..a7c22a5 100644 --- a/docs/architecture.md +++ b/docs/internals/architecture.md @@ -14,7 +14,7 @@ Colony is an application launcher written in Rust with Iced 0.14 (Elm architectu | Async | Tokio (integrated via Iced runtime) | | HTTP | reqwest (async + streaming), rustls checking certificates against the OS trust store (no OpenSSL) | | Auth | GitHub Device Flow OAuth | -| Secret storage | keyring (OS keychain) + file fallback | +| Secret storage | keyring-core with the native store of each OS (Keychain, Credential Manager, Secret Service) + file fallback | | Serialization | serde + serde_json | | Configuration | TOML (scan dirs), JSON (categories, preferences) | | Versioning | semver | @@ -94,8 +94,8 @@ All async operations (API calls, downloads, scanning) return a `Task` t ## Security - **OAuth**: Device Flow (no client_secret exposed) -- **Tokens**: Stored in OS keychain, file fallback (chmod 600) -- **Downloads**: HTTPS only, optional SHA256 verification, resumable via Range with an ETag+length identity check before any partial file is continued +- **Tokens**: Stored in the OS credential store, file fallback (chmod 600). The file is deleted only after the token reads back from the store +- **Downloads**: HTTPS only, ed25519 signature and signed `.meta` checks when the release publishes them (pinned once seen, see [release-signing.md](release-signing.md)), optional SHA256 from the manifest, resumable via Range with an ETag+length identity check before any partial file is continued - **URLs**: every remote-controlled path segment is percent-encoded, never interpolated - the WHATWG parser collapses `..` before the request is sent - **Timeouts**: 30s total for API requests, 10s connect, 60s read (inactivity) for downloads - a total deadline on a download makes a large asset impossible to fetch on a slow line - **Self-update**: Binary backup before replacement, automatic rollback on failure @@ -112,10 +112,12 @@ All async operations (API calls, downloads, scanning) return a `Task` t | Repo icons (cache) | `~/.cache/Colony/Colony/repo-icons//` | Offline fallback | | Preferences | `~/.config/Colony/Colony/preferences/preferences.json` | Permanent | | Favorites | `~/.config/Colony/Colony/preferences/favorites.json` | Permanent | -| OAuth token | OS Keychain / `~/.config/Colony/Colony/auth/github_token.json` | Permanent | +| OAuth token | OS credential store; `~/.config/Colony/Colony/auth/github_token.json` only as the fallback | Until sign-out | | Diagnostics log | `~/.local/share/Colony/Colony/colony.log` | Truncated per run | | Installed versions | `~/.local/share/Colony/apps//.colony_version` | Permanent | | Resolved asset | `~/.local/share/Colony/apps//.colony_asset` | Permanent | +| Signature pins | `~/.local/share/Colony/apps//.colony_signed`, `.colony_meta` | Permanent | +| Menu entries (Linux) | `~/.local/share/applications/colony-.desktop` | Until uninstall | | Colony binaries | `~/.local/share/Colony/apps//` | Permanent | | Self-update staging | `~/.cache/Colony/Colony/update-staging/` | Emptied at every start | diff --git a/docs/release-signing.md b/docs/internals/release-signing.md similarity index 98% rename from docs/release-signing.md rename to docs/internals/release-signing.md index 71413d5..3206479 100644 --- a/docs/release-signing.md +++ b/docs/internals/release-signing.md @@ -1,7 +1,7 @@ # Release signing Colony checks an **ed25519 signature** before it runs anything it downloads. -The trusted public keys are embedded in [`src/signing.rs`](../src/signing.rs) +The trusted public keys are embedded in [`src/signing.rs`](../../src/signing.rs) (`RELEASE_PUBLIC_KEYS`) and verified with the pure-Rust `ed25519-dalek` crate, so the shipped binary needs no OpenSSL. @@ -18,7 +18,7 @@ so the shipped binary needs no OpenSSL. `"signed": true`. Once an install of an app has verified a signature, that app is pinned: a later release that stops publishing `.sig` is refused, whatever the manifest now says. The same applies to the `.meta` sidecar - ([`src/download/install.rs`](../src/download/install.rs), `download_release_asset`). The + ([`src/download/install.rs`](../../src/download/install.rs), `download_release_asset`). The optional `sha256` field in `colony.json` is checked on top of that. The organisation signs every program with one key, so the keys embedded here diff --git a/docs/tutorial.md b/docs/tutorial.md deleted file mode 100644 index 1b34ab9..0000000 --- a/docs/tutorial.md +++ /dev/null @@ -1,158 +0,0 @@ -# Colony — Tutorial - -This is a step-by-step walkthrough for first-time Colony users. By the end of -it you'll have Colony installed, understand its interface, installed your first -app from the catalog, and customized themes + preferences. - -## Prerequisites - -A Linux, Windows, or macOS desktop and ~50 MB of free disk space for Colony -itself. Additional space per Colony app you install (typically 20–100 MB each). - -## 1. Install Colony - -Pick the path that matches your system. All roads lead to the same `colony` -binary and desktop entry. - -### Arch Linux - -```bash -paru -S colony-bin # prebuilt, fastest -# or -paru -S colony-git # builds from source, always at HEAD -``` - -### Other Linux distributions - -Download `colony-linux` from the -[latest release](https://github.com/Project-Colony/Colony/releases/latest), -make it executable, and run: - -```bash -curl -L -o colony https://github.com/Project-Colony/Colony/releases/latest/download/colony-linux -chmod +x colony -./colony -``` - -Optional: move it to `~/.local/bin/colony` to have it in your `$PATH`, and -create a matching `~/.local/share/applications/colony.desktop` so your launcher -picks it up (Colony ships this file automatically when installed via AUR). - -### Windows - -Download `colony-windows.exe` from the -[latest release](https://github.com/Project-Colony/Colony/releases/latest) and -double-click to run. Windows Defender may prompt — the binary is unsigned for -now; click "More info → Run anyway". - -### macOS - -Download `colony-macos` (Apple Silicon) or `colony-macos-x86` (Intel) from the -[latest release](https://github.com/Project-Colony/Colony/releases/latest). - -```bash -chmod +x colony-macos -xattr -d com.apple.quarantine colony-macos # remove Gatekeeper quarantine -./colony-macos -``` - -## 2. First launch — the main interface - -When you open Colony for the first time you'll see three zones: - -``` -┌───────────┬──────────────────────────────────────────┐ -│ │ │ -│ Sidebar │ Main panel (app grid) │ -│ │ │ -│ ───── │ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ All │ │ │ │ │ │ │ │ -│ Windows │ │ App 1 │ │ App 2 │ │ App 3 │ │ -│ Linux │ │ │ │ │ │ │ │ -│ ───── │ └────────┘ └────────┘ └────────┘ │ -│ Develop. │ │ -│ Graphics │ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ Network │ │ │ │ │ │ │ │ -│ … │ │ App 4 │ │ App 5 │ │ App 6 │ │ -│ │ │ │ │ │ │ │ │ -│ GitHub ⚙ │ └────────┘ └────────┘ └────────┘ │ -└───────────┴──────────────────────────────────────────┘ -``` - -- **Sidebar**: Categories. The top group (`All` / `Windows` / `Linux`) is - the *origin* filter: Colony apps, apps already installed on your system, or - both. The middle group is Colony categories (`Development`, `Graphics`, - `Network`, `Office`, `Multimedia`, `System`, `Utilities`, `Security`, `Games`, - `Other`). -- **App grid**: Cards for every app matching the current filters. Click a - card to open its detail view. -- **Search**: A search bar above the grid filters by app name. -- **Colony**: The name at the top of the sidebar opens Preferences: General, - Appearance, Accessibility, Storage, Shortcuts and About. Click it again, or - Close, to go back. - -## 3. Install your first Colony app - -1. Pick a category in the sidebar (for example **Multimedia**). -2. Click the app card you want. -3. The detail view opens: - - Name, description (pulled from the upstream README) - - Current installed version vs. latest available - - Category, supported platforms, license, changelog - - A large **Install** button (or **Update** / **Launch** if already installed) -4. Click **Install**. Colony downloads the appropriate asset for your platform - (e.g. `grape-linux`), verifies SHA256 when provided, and stores the binary - under `~/.local/share/Colony/apps//`. -5. When the download completes, **Install** becomes **Launch**. Click it — - the app starts as a separate process, independent of Colony. - -You can also launch the app later from your system launcher (rofi/wofi/GNOME -activities) if the app installed its own `.desktop` file. - -## 4. System apps — not just Colony apps - -Colony scans your system for already-installed applications: - -- **Linux**: `.desktop` files in `~/.local/share/applications`, `/usr/share/applications`, and flatpak locations. -- **Windows**: Start Menu entries. -- **macOS**: `.app` bundles under `/Applications` and `~/Applications`. - -Switch the sidebar origin to **Linux** (or **Windows**) to see only those. -**All** merges Colony's catalog with your local apps. - -## 5. Customize the theme - -Click **Colony** at the top of the sidebar, then **Appearance** → **Theme**. -Colony ships 26 theme families and 59 palettes (Catppuccin, Gruvbox, Nord, -Dracula, Rosé Pine, Tokyo Night, etc.). Theme changes apply instantly, no -restart needed. - -## 6. Connect your GitHub account (optional) - -Without authentication Colony uses the public GitHub API: 60 requests per hour. -If you browse a lot of apps, connect your account for a 5000 req/h rate limit: - -1. Open Settings → **GitHub** tab (or click the sidebar footer). -2. Click **Connect**. -3. Colony displays a device code. Open the URL it shows, paste the code, authorize. -4. Colony stores the token in your OS keychain (with a chmod-600 file fallback). -5. Disconnect any time — the token is deleted locally. - -No scopes beyond public-repo reads are requested. - -## 7. Keep Colony up to date - -Colony checks for its own updates periodically. When a new version is -available, a badge appears next to the sidebar footer — click it to download, -then restart. On AUR you can also just run `paru -Syu` and Colony will be -upgraded alongside the rest of your system. - -## Troubleshooting - -- **App card not showing up?** Verify the repo has a `colony.json` at root and - a published release with assets matching `-`. See - [colony-spec.md](colony-spec.md) for the full manifest reference. -- **Rate limit hit?** Connect a GitHub account (section 6). -- **Download fails?** Check your network. Colony retries up to three times and resumes where it stopped; logs - go to `~/.local/share/Colony/Colony/colony.log` if you need to dig in. -- **More questions?** See the [FAQ](faq.md). diff --git a/src/github/mod.rs b/src/github/mod.rs index 7c87b16..f7ce18b 100644 --- a/src/github/mod.rs +++ b/src/github/mod.rs @@ -333,7 +333,7 @@ mod tests { #[test] fn spec_conformant_manifest_parses_field_for_field() { - // Locks docs/colony-spec.md <-> code parity: this sample uses every + // Locks docs/guide/colony-spec.md <-> code parity: this sample uses every // documented manifest field with the spec's exact camelCase names. // If a rename or removal breaks the spec, this test fails first. let json = r#"{ diff --git a/src/github/releases.rs b/src/github/releases.rs index ddb54fc..d3cab92 100644 --- a/src/github/releases.rs +++ b/src/github/releases.rs @@ -131,7 +131,7 @@ const NON_INSTALLABLE_SUFFIXES: &[&str] = &[ // already ship `colony-linux.meta`, so the day an ecosystem app adopts the // sidecar, a documented legacy substring pattern like "linux" would start // matching two assets and fail with "Ambiguous pattern" - which is exactly - // what docs/colony-spec.md promises cannot happen. + // what docs/guide/colony-spec.md promises cannot happen. ".meta", ".sha256", ".sha256sum", diff --git a/src/oauth.rs b/src/oauth.rs index 885d385..3a13be2 100644 --- a/src/oauth.rs +++ b/src/oauth.rs @@ -1,5 +1,7 @@ use anyhow::Result; -use std::path::PathBuf; +use keyring_core::{CredentialStore, Entry}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::time::Duration; /// GitHub OAuth App Client ID. @@ -219,11 +221,38 @@ async fn fetch_username(token: &str) -> Result { } // --- Token persistence --- -// Uses OS keychain (via keyring crate) with file fallback. +// The OS credential store (keyring 4) first, a 0600 file as the fallback. const KEYRING_SERVICE: &str = "colony-launcher"; const KEYRING_USER: &str = "github-oauth"; +/// The OS credential store for this target. +/// +/// Built per call rather than once: the three callers run once per launch, +/// login and logout, and a Secret Service daemon that starts after Colony is +/// then picked up instead of being missed for the whole session. There is no +/// arm for other targets on purpose: keyring 3 quietly fell back to an +/// in-memory mock there, so a missing store is now a build error instead. +fn os_store() -> keyring_core::Result> { + #[cfg(target_os = "macos")] + let store = apple_native_keyring_store::keychain::Store::new()?; + #[cfg(windows)] + let store = windows_native_keyring_store::Store::new()?; + #[cfg(all( + unix, + not(any(target_os = "macos", target_os = "ios", target_os = "android")) + ))] + let store = dbus_secret_service_keyring_store::Store::new()?; + Ok(store) +} + +/// The token's entry in the OS credential store. The service and user match +/// keyring 3, whose stores used the same identifiers on every OS, so a token +/// saved before the keyring 4 upgrade is read and overwritten in place. +fn os_entry() -> keyring_core::Result { + os_store()?.build(KEYRING_SERVICE, KEYRING_USER, None) +} + fn token_path() -> PathBuf { match crate::persistence::colony_data_dir() { Ok(dir) => { @@ -249,37 +278,33 @@ struct StoredToken { } fn save_token(session: &OAuthSession) -> Result<()> { + save_token_with(os_entry(), &token_path(), session) +} + +fn save_token_with( + entry: keyring_core::Result, + path: &Path, + session: &OAuthSession, +) -> Result<()> { let stored = StoredToken { access_token: session.access_token.clone(), username: session.username.clone(), }; let json = serde_json::to_string_pretty(&stored)?; - // Try the OS keychain first. - let keychain_ok = match keyring::Entry::new(KEYRING_SERVICE, KEYRING_USER) { - Ok(entry) => match entry.set_password(&json) { - Ok(()) => { - tracing::info!("Token saved to OS keychain"); - true - } - Err(e) => { - tracing::warn!("Keychain unavailable ({e}); using file fallback"); - false + match store_verified(entry, &json) { + Ok(()) => { + tracing::info!("Token saved to the OS credential store"); + // The store holds a copy that read back intact: never leave a + // plaintext one on disk. + if path.exists() { + if let Err(e) = std::fs::remove_file(path) { + tracing::warn!("could not delete the plaintext token file: {e}"); + } } - }, - Err(e) => { - tracing::warn!("Keychain error ({e}); using file fallback"); - false + return Ok(()); } - }; - - let path = token_path(); - if keychain_ok { - // Keychain holds the secret — never leave a plaintext copy on disk. - if path.exists() { - let _ = std::fs::remove_file(&path); - } - return Ok(()); + Err(why) => tracing::warn!("{why}; using the file fallback"), } // Fallback only: write the token to a file created with 0600 from the start @@ -287,11 +312,28 @@ fn save_token(session: &OAuthSession) -> Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } - write_private(&path, json.as_bytes())?; + write_private(path, json.as_bytes())?; tracing::warn!("Token saved to plaintext file fallback: {}", path.display()); Ok(()) } +/// Store the secret, then read it back. Only an identical read-back counts as +/// saved: an Ok from `set_password` alone is what keyring 3's mock returned +/// while keeping nothing, and trusting it cost users the plaintext copy. +fn store_verified(entry: keyring_core::Result, secret: &str) -> Result<(), String> { + let entry = entry.map_err(|e| format!("OS credential store unavailable ({e})"))?; + entry + .set_password(secret) + .map_err(|e| format!("OS credential store refused the token ({e})"))?; + match entry.get_password() { + Ok(back) if back == secret => Ok(()), + Ok(_) => Err("OS credential store returned a different token on read-back".into()), + Err(e) => Err(format!( + "OS credential store lost the token on read-back ({e})" + )), + } +} + /// Write a file that is owner-only (0600) from creation on Unix, avoiding the /// world-readable window of write-then-chmod. fn write_private(path: &std::path::Path, contents: &[u8]) -> Result<()> { @@ -325,24 +367,30 @@ fn write_private(path: &std::path::Path, contents: &[u8]) -> Result<()> { } /// Try to load a previously stored token. -/// Checks OS keychain first, then falls back to file. +/// Checks the OS credential store first, then falls back to the file. pub fn load_saved_token() -> Option { - // Try keychain first - if let Ok(entry) = keyring::Entry::new(KEYRING_SERVICE, KEYRING_USER) { - if let Ok(json) = entry.get_password() { - if let Ok(stored) = serde_json::from_str::(&json) { - tracing::debug!("Token loaded from OS keychain"); + load_token_with(os_entry(), &token_path()) +} + +fn load_token_with(entry: keyring_core::Result, path: &Path) -> Option { + match entry.and_then(|entry| entry.get_password()) { + Ok(json) => match serde_json::from_str::(&json) { + Ok(stored) => { + tracing::debug!("Token loaded from the OS credential store"); return Some(OAuthSession { access_token: stored.access_token, username: stored.username, }); } - } + Err(e) => { + tracing::warn!("ignoring an unreadable token in the OS credential store: {e}") + } + }, + Err(keyring_core::Error::NoEntry) => {} + Err(e) => tracing::debug!("OS credential store not read ({e}); trying the file"), } - // Fallback: file-based - let path = token_path(); - let content = std::fs::read_to_string(&path).ok()?; + let content = std::fs::read_to_string(path).ok()?; let stored: StoredToken = serde_json::from_str(&content).ok()?; tracing::debug!("Token loaded from file: {}", path.display()); Some(OAuthSession { @@ -360,26 +408,23 @@ pub fn load_saved_token() -> Option { /// so a failed deletion still reported "Disconnected" and the next launch /// silently restored the session from the credential the user just revoked. pub fn delete_saved_token() -> Result<()> { + delete_token_with(os_entry(), &token_path()) +} + +fn delete_token_with(entry: keyring_core::Result, path: &Path) -> Result<()> { let mut failure: Option = None; - match keyring::Entry::new(KEYRING_SERVICE, KEYRING_USER) { - Ok(entry) => match entry.delete_credential() { - // Nothing stored is the desired end state, not an error. - Ok(()) | Err(keyring::Error::NoEntry) => {} - Err(e) => { - tracing::warn!("could not delete the keychain credential: {e}"); - failure = Some(e.to_string()); - } - }, + match entry.and_then(|entry| entry.delete_credential()) { + // Nothing stored is the desired end state, not an error. + Ok(()) | Err(keyring_core::Error::NoEntry) => {} Err(e) => { - tracing::warn!("could not open the keychain entry to delete it: {e}"); + tracing::warn!("could not delete the credential from the OS store: {e}"); failure = Some(e.to_string()); } } - let path = token_path(); if path.exists() { - if let Err(e) = std::fs::remove_file(&path) { + if let Err(e) = std::fs::remove_file(path) { tracing::warn!("could not delete the token file: {e}"); failure = Some(e.to_string()); } @@ -393,3 +438,145 @@ pub fn delete_saved_token() -> Result<()> { Some(e) => anyhow::bail!("{e}"), } } + +#[cfg(test)] +mod tests { + use super::*; + use keyring_core::api::{CredentialApi, CredentialStoreApi}; + use keyring_core::{mock, Credential, Error}; + use std::any::Any; + + // The mock store is used here and nowhere else: production code never + // sets a default store, and `os_store` has no mock arm. + fn mock_entry(store: &Arc) -> keyring_core::Result { + store.build(KEYRING_SERVICE, KEYRING_USER, None) + } + + fn session(token: &str) -> OAuthSession { + OAuthSession { + access_token: token.to_string(), + username: Some("octocat".to_string()), + } + } + + /// keyring 3's trap, reproduced: the write answers Ok and keeps nothing. + #[derive(Debug)] + struct Forgetful; + + impl CredentialApi for Forgetful { + fn set_secret(&self, _: &[u8]) -> keyring_core::Result<()> { + Ok(()) + } + fn get_secret(&self) -> keyring_core::Result> { + Err(Error::NoEntry) + } + fn delete_credential(&self) -> keyring_core::Result<()> { + Err(Error::NoEntry) + } + fn get_credential(&self) -> keyring_core::Result>> { + Ok(None) + } + fn get_specifiers(&self) -> Option<(String, String)> { + None + } + fn as_any(&self) -> &dyn Any { + self + } + } + + #[test] + fn os_store_is_the_native_store_never_the_mock() { + let expected = if cfg!(target_os = "macos") { + "macOS Keychain" + } else if cfg!(windows) { + "Windows Credential Manager" + } else { + "Secret Service" + }; + match os_store() { + Ok(store) => assert!( + store.vendor().starts_with(expected), + "unexpected credential store: {}", + store.vendor() + ), + // The Keychain and Credential Manager stores connect to nothing, + // so they must always build. + Err(e) if cfg!(any(target_os = "macos", windows)) => { + panic!("the native store failed to build: {e}") + } + // Building the Secret Service store connects to the session bus, + // which a CI runner may not have. + Err(_) => {} + } + } + + #[test] + fn verified_save_removes_the_plaintext_copy() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("github_token.json"); + std::fs::write(&path, "stale").unwrap(); + let store = mock::Store::new().unwrap(); + + save_token_with(mock_entry(&store), &path, &session("gho_new")).unwrap(); + + assert!( + !path.exists(), + "the plaintext copy must go once the store holds the token" + ); + let loaded = load_token_with(mock_entry(&store), &path).unwrap(); + assert_eq!(loaded.access_token, "gho_new"); + assert_eq!(loaded.username.as_deref(), Some("octocat")); + } + + #[test] + fn a_write_that_does_not_read_back_keeps_the_plaintext_copy() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("github_token.json"); + let forgetful = Entry::new_with_credential(Arc::new(Forgetful)); + + save_token_with(Ok(forgetful), &path, &session("gho_kept")).unwrap(); + + let loaded = load_token_with(Err(Error::NoDefaultStore), &path).unwrap(); + assert_eq!(loaded.access_token, "gho_kept"); + } + + #[test] + fn a_refused_write_keeps_the_plaintext_copy() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("github_token.json"); + let store = mock::Store::new().unwrap(); + let entry = mock_entry(&store).unwrap(); + let cred: &mock::Cred = entry.as_any().downcast_ref().unwrap(); + cred.set_error(Error::NoStorageAccess("locked".into())); + + save_token_with(Ok(entry), &path, &session("gho_file")).unwrap(); + + let loaded = load_token_with(Err(Error::NoDefaultStore), &path).unwrap(); + assert_eq!(loaded.access_token, "gho_file"); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mode = std::fs::metadata(&path).unwrap().permissions().mode(); + assert_eq!(mode & 0o777, 0o600); + } + } + + #[test] + fn logout_clears_both_copies_and_tolerates_an_empty_store() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("github_token.json"); + let store = mock::Store::new().unwrap(); + mock_entry(&store) + .unwrap() + .set_password("{\"access_token\":\"gho_x\",\"username\":null}") + .unwrap(); + std::fs::write(&path, "{\"access_token\":\"gho_x\",\"username\":null}").unwrap(); + + delete_token_with(mock_entry(&store), &path).unwrap(); + + assert!(!path.exists()); + assert!(load_token_with(mock_entry(&store), &path).is_none()); + // Nothing left to delete is the desired end state, not a failure. + delete_token_with(mock_entry(&store), &path).unwrap(); + } +} diff --git a/src/signing.rs b/src/signing.rs index 69b680a..83af7ef 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -2,7 +2,7 @@ //! //! Colony's own release binaries are signed with an ed25519 private key that //! only the organisation's shared signing workflow can use (see -//! `docs/release-signing.md`); the matching public key is embedded below. +//! `docs/internals/release-signing.md`); the matching public key is embedded below. //! Before a launcher self-update is applied, the downloaded binary is verified //! against a detached `.sig` signature. Verification is mandatory and //! fail-closed: a missing, malformed, or invalid signature aborts the update @@ -36,7 +36,7 @@ use ed25519_dalek::{Signature, VerifyingKey}; /// 3. Drop `old` from this list in N+2 to complete the revocation. /// /// Order is irrelevant to verification; keep the newest first for readability. -/// See `docs/release-signing.md`. +/// See `docs/internals/release-signing.md`. const RELEASE_PUBLIC_KEYS: &[[u8; 32]] = &[[ 0x44, 0xd8, 0xe0, 0xdc, 0xd9, 0xfc, 0x1f, 0xaf, 0xda, 0x06, 0x0d, 0x6e, 0x9f, 0x01, 0xa3, 0x91, 0x44, 0xdc, 0xad, 0xd4, 0xf1, 0x11, 0x13, 0x5e, 0x7d, 0x56, 0xaa, 0x53, 0xc7, 0x05, 0xbb, 0x4b, diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 5fa5754..54ffe28 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -92,8 +92,8 @@ mod doc_parity_tests { assert_eq!( families, 26, "the docs say 26 theme families; colony-ui ships {families}. Update README.md \ - (including its table of family names), docs/faq.md, docs/tutorial.md, \ - docs/architecture.md and CONTRIBUTING.md." + (including its table of family names), docs/guide/faq.md, \ + docs/guide/tutorial.md, docs/internals/architecture.md and CONTRIBUTING.md." ); assert_eq!( palettes, 59,