A social internet radio platform built on AT Protocol. atradio.fm
atproto radio
7

Configure Feed

Select the types of activity you want to include in your feed.

build(cli): nix flakes, release workflow, packaging, install docs

- Make cli/ a standalone crate (own Cargo.lock, hardcoded manifest,
release profile) so it can be its own flake; drop the single-member
root Cargo workspace.
- cli/flake.nix: crane+fenix build (packages.default = atradio), checks,
and a nix develop shell with the rust toolchain + ALSA/CoreAudio deps.
- Root flake.nix: delegate default/apps/devShells to the ./cli sub-flake
so nix run / nix profile install github:tsirysndr/atradio.fm resolve to
the CLI. Both flake.lock files generated.
- .github/workflows/release.yml: build macOS + Linux (x86_64/aarch64),
package tar.gz + .deb + .rpm, publish a GitHub Release, and push
packages to Gemfury (skipped if FURY_TOKEN/FURY_ACCOUNT unset).
- .github/workflows/nix.yml: nix build + flake check on ubuntu + macos
with Cachix caching (CACHIX_AUTH_TOKEN).
- cli/dist: deb control (amd64/arm64), rpm spec, homebrew formula
template. cli/LICENSE: GPL-2.0 (rockbox linkage).
- cli/README: install docs (Homebrew, apt/dnf via Gemfury, Nix, source),
preview image, aligned keybindings; drop "synthwave" wording.

Tsiry Sandratraina (Jul 17, 2026, 2:05 AM +0300) 2feba31b 20cd580b

+1240 -54
+45
.github/workflows/nix.yml
··· 1 + name: nix 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + paths: 7 + - "cli/**" 8 + - "flake.nix" 9 + - "flake.lock" 10 + - ".github/workflows/nix.yml" 11 + pull_request: 12 + paths: 13 + - "cli/**" 14 + - "flake.nix" 15 + - "flake.lock" 16 + - ".github/workflows/nix.yml" 17 + workflow_dispatch: 18 + 19 + jobs: 20 + build: 21 + name: nix build (${{ matrix.os }}) 22 + strategy: 23 + fail-fast: false 24 + matrix: 25 + os: [ubuntu-latest, macos-latest] 26 + runs-on: ${{ matrix.os }} 27 + steps: 28 + - uses: actions/checkout@v4 29 + 30 + - uses: cachix/install-nix-action@v30 31 + with: 32 + extra_nix_config: | 33 + experimental-features = nix-command flakes 34 + 35 + - uses: cachix/cachix-action@v15 36 + with: 37 + name: tsirysndr 38 + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 39 + 40 + # Build the root default package (delegates to the ./cli sub-flake). 41 + - name: nix build 42 + run: nix build -L 43 + 44 + - name: nix flake check 45 + run: nix flake check -L
+172
.github/workflows/release.yml
··· 1 + name: release 2 + 3 + on: 4 + push: 5 + tags: ["v*"] 6 + workflow_dispatch: 7 + inputs: 8 + tag: 9 + description: "Tag to release (e.g. v0.1.0)" 10 + required: true 11 + type: string 12 + 13 + permissions: 14 + contents: write 15 + 16 + jobs: 17 + build: 18 + name: build (${{ matrix.label }}) 19 + runs-on: ${{ matrix.os }} 20 + # The CLI is a self-contained crate under cli/; run all build steps there. 21 + defaults: 22 + run: 23 + working-directory: cli 24 + strategy: 25 + fail-fast: false 26 + matrix: 27 + include: 28 + - label: macos-amd64 29 + os: macos-latest 30 + target: x86_64-apple-darwin 31 + - label: macos-aarch64 32 + os: macos-latest 33 + target: aarch64-apple-darwin 34 + - label: linux-amd64 35 + os: ubuntu-latest 36 + target: x86_64-unknown-linux-gnu 37 + - label: linux-aarch64 38 + os: ubuntu-24.04-arm 39 + target: aarch64-unknown-linux-gnu 40 + steps: 41 + - uses: actions/checkout@v4 42 + with: 43 + ref: ${{ inputs.tag || github.ref }} 44 + 45 + - uses: dtolnay/rust-toolchain@stable 46 + with: 47 + targets: ${{ matrix.target }} 48 + 49 + - uses: Swatinem/rust-cache@v2 50 + with: 51 + workspaces: cli 52 + key: ${{ matrix.target }} 53 + 54 + - name: Install Linux build deps 55 + if: startsWith(matrix.os, 'ubuntu') 56 + run: sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config 57 + 58 + - name: Build 59 + run: cargo build --release --locked --target ${{ matrix.target }} --bin atradio 60 + 61 + - name: Package tarball 62 + run: | 63 + set -euo pipefail 64 + version="${{ inputs.tag || github.ref_name }}" 65 + name="atradio-${version}-${{ matrix.label }}" 66 + rm -rf dist/staging && mkdir -p dist/staging 67 + cp "target/${{ matrix.target }}/release/atradio" dist/staging/ 68 + cp README.md LICENSE dist/staging/ 69 + tar -C dist/staging -czf "dist/${name}.tar.gz" . 70 + shasum -a 256 "dist/${name}.tar.gz" | awk '{print $1}' > "dist/${name}.tar.gz.sha256" 71 + 72 + - name: Install packaging tools 73 + if: startsWith(matrix.os, 'ubuntu') 74 + run: sudo apt-get install -y dpkg-dev rpm 75 + 76 + - name: Build .deb 77 + if: startsWith(matrix.os, 'ubuntu') 78 + run: | 79 + set -euo pipefail 80 + version="${{ inputs.tag || github.ref_name }}" 81 + version_no_v="${version#v}" 82 + case "${{ matrix.label }}" in 83 + linux-amd64) deb_arch=amd64 ;; 84 + linux-aarch64) deb_arch=arm64 ;; 85 + esac 86 + pkg_root="dist/debian/${deb_arch}" 87 + install -Dm0755 "target/${{ matrix.target }}/release/atradio" "${pkg_root}/usr/local/bin/atradio" 88 + sed -i "s/^Version: .*/Version: ${version_no_v}/" "${pkg_root}/DEBIAN/control" 89 + dpkg-deb --build --root-owner-group "${pkg_root}" "dist/atradio_${version_no_v}_${deb_arch}.deb" 90 + 91 + - name: Build .rpm 92 + if: matrix.label == 'linux-amd64' 93 + run: | 94 + set -euo pipefail 95 + version="${{ inputs.tag || github.ref_name }}" 96 + version_no_v="${version#v}" 97 + src_root="dist/rpm/amd64" 98 + install -Dm0755 "target/${{ matrix.target }}/release/atradio" "${src_root}/usr/local/bin/atradio" 99 + sed -i "s/^Version: .*/Version: ${version_no_v}/" "${src_root}/atradio.spec" 100 + top="$(pwd)/dist/rpmbuild" 101 + mkdir -p "${top}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} 102 + rm -rf "${top}/SOURCES/amd64" && cp -r "${src_root}" "${top}/SOURCES/amd64" 103 + rpmbuild \ 104 + --define "_topdir ${top}" \ 105 + --define "_sourcedir ${top}/SOURCES" \ 106 + --target x86_64 \ 107 + -bb "${src_root}/atradio.spec" 108 + find "${top}/RPMS" -name '*.rpm' -exec mv {} dist/ \; 109 + 110 + - name: Checksum packages 111 + if: startsWith(matrix.os, 'ubuntu') 112 + run: | 113 + set -euo pipefail 114 + shopt -s nullglob 115 + for f in dist/*.deb dist/*.rpm; do 116 + shasum -a 256 "$f" | awk '{print $1}' > "$f.sha256" 117 + done 118 + 119 + - uses: actions/upload-artifact@v4 120 + with: 121 + name: ${{ matrix.label }} 122 + path: | 123 + cli/dist/*.tar.gz* 124 + cli/dist/*.deb* 125 + cli/dist/*.rpm* 126 + if-no-files-found: error 127 + 128 + release: 129 + name: publish release 130 + needs: [build] 131 + runs-on: ubuntu-latest 132 + steps: 133 + - uses: actions/download-artifact@v4 134 + with: 135 + path: dist 136 + merge-multiple: true 137 + 138 + - name: Create GitHub Release 139 + uses: softprops/action-gh-release@v2 140 + with: 141 + tag_name: ${{ github.event.inputs.tag || github.ref_name }} 142 + files: dist/* 143 + generate_release_notes: true 144 + draft: false 145 + prerelease: false 146 + 147 + - name: Check Gemfury secrets 148 + id: gemfury 149 + env: 150 + FURY_TOKEN: ${{ secrets.FURY_TOKEN }} 151 + FURY_ACCOUNT: ${{ secrets.FURY_ACCOUNT }} 152 + run: | 153 + if [ -n "${FURY_TOKEN}" ] && [ -n "${FURY_ACCOUNT}" ]; then 154 + echo "available=true" >> "$GITHUB_OUTPUT" 155 + else 156 + echo "available=false" >> "$GITHUB_OUTPUT" 157 + echo "FURY_TOKEN / FURY_ACCOUNT not set — skipping Gemfury push." 158 + fi 159 + 160 + - name: Push .deb / .rpm to Gemfury 161 + if: steps.gemfury.outputs.available == 'true' 162 + env: 163 + FURY_TOKEN: ${{ secrets.FURY_TOKEN }} 164 + FURY_ACCOUNT: ${{ secrets.FURY_ACCOUNT }} 165 + run: | 166 + set -euo pipefail 167 + shopt -s nullglob 168 + for pkg in dist/*.deb dist/*.rpm; do 169 + echo "Uploading ${pkg} to Gemfury (${FURY_ACCOUNT})…" 170 + curl -fsS -F package=@"${pkg}" \ 171 + "https://${FURY_TOKEN}@push.fury.io/${FURY_ACCOUNT}/" 172 + done
+5
.gitignore
··· 17 17 # Rust (cli/ — the atradio TUI crate) 18 18 target 19 19 **/*.rs.bk 20 + 21 + # cli/dist holds source-controlled packaging templates (deb/rpm/homebrew), 22 + # not build output — keep it despite the `dist` rule above. 23 + !cli/dist 24 + !cli/dist/**
+2 -2
Cargo.lock cli/Cargo.lock
··· 451 451 452 452 [[package]] 453 453 name = "borsh" 454 - version = "1.7.0" 454 + version = "1.8.0" 455 455 source = "registry+https://github.com/rust-lang/crates.io-index" 456 - checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" 456 + checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" 457 457 dependencies = [ 458 458 "bytes", 459 459 "cfg_aliases",
-11
Cargo.toml
··· 1 - [workspace] 2 - members = ["cli"] 3 - resolver = "2" 4 - 5 - [workspace.package] 6 - authors = ["Tsiry Sandratraina <tsiry.sndr@rocksky.app"] 7 - edition = "2021" 8 - license = "MPL-2.0" 9 - repository = "https://github.com/tsirysndr/atradio.fm" 10 - 11 - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+8 -8
cli/Cargo.toml
··· 2 2 name = "atradio" 3 3 version = "0.1.0" 4 4 edition = "2021" 5 - description = "atradio.fm in your terminal — a synthwave TUI radio player on the AT Protocol" 5 + description = "atradio.fm in your terminal — a TUI radio player on the AT Protocol" 6 6 # NOTE: rockbox-playback is GPL-2.0-or-later; linking it makes this binary GPL. 7 7 license = "GPL-2.0-or-later" 8 - authors.workspace = true 9 - repository.workspace = true 8 + authors = ["Tsiry Sandratraina <tsiry.sndr@rocksky.app>"] 9 + repository = "https://github.com/tsirysndr/atradio.fm" 10 10 11 11 [[bin]] 12 12 name = "atradio" 13 13 path = "src/main.rs" 14 14 15 15 [dependencies] 16 - # atproto (auth + PDS writes). Minimal `api` feature = com.atproto only 17 - # (createSession + repo.putRecord); we skip the 646-lexicon Bluesky/community 18 - # codegen. `loopback` enables the OAuth browser login flow. 19 16 jacquard = { version = "0.12", default-features = false, features = [ 20 17 "api", 21 18 "dns", ··· 23 20 "cache", 24 21 "loopback", 25 22 ] } 26 - # Direct deps so the code generated from our lexicons (which references these by 27 - # crate name) resolves. Same versions jacquard already pulls in. 28 23 jacquard-common = "0.12" 29 24 jacquard-lexicon = "0.12" 30 25 jacquard-derive = "0.12" ··· 61 56 default = ["fm_atradio"] 62 57 # Gates the code generated from packages/lexicons/lexicons/atradio. 63 58 fm_atradio = [] 59 + 60 + [profile.release] 61 + opt-level = 3 62 + lto = "thin" 63 + strip = true
+338
cli/LICENSE
··· 1 + GNU GENERAL PUBLIC LICENSE 2 + Version 2, June 1991 3 + 4 + Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 + <https://fsf.org/> 6 + Everyone is permitted to copy and distribute verbatim copies 7 + of this license document, but changing it is not allowed. 8 + 9 + Preamble 10 + 11 + The licenses for most software are designed to take away your 12 + freedom to share and change it. By contrast, the GNU General Public 13 + License is intended to guarantee your freedom to share and change free 14 + software--to make sure the software is free for all its users. This 15 + General Public License applies to most of the Free Software 16 + Foundation's software and to any other program whose authors commit to 17 + using it. (Some other Free Software Foundation software is covered by 18 + the GNU Lesser General Public License instead.) You can apply it to 19 + your programs, too. 20 + 21 + When we speak of free software, we are referring to freedom, not 22 + price. Our General Public Licenses are designed to make sure that you 23 + have the freedom to distribute copies of free software (and charge for 24 + this service if you wish), that you receive source code or can get it 25 + if you want it, that you can change the software or use pieces of it 26 + in new free programs; and that you know you can do these things. 27 + 28 + To protect your rights, we need to make restrictions that forbid 29 + anyone to deny you these rights or to ask you to surrender the rights. 30 + These restrictions translate to certain responsibilities for you if you 31 + distribute copies of the software, or if you modify it. 32 + 33 + For example, if you distribute copies of such a program, whether 34 + gratis or for a fee, you must give the recipients all the rights that 35 + you have. You must make sure that they, too, receive or can get the 36 + source code. And you must show them these terms so they know their 37 + rights. 38 + 39 + We protect your rights with two steps: (1) copyright the software, and 40 + (2) offer you this license which gives you legal permission to copy, 41 + distribute and/or modify the software. 42 + 43 + Also, for each author's protection and ours, we want to make certain 44 + that everyone understands that there is no warranty for this free 45 + software. If the software is modified by someone else and passed on, we 46 + want its recipients to know that what they have is not the original, so 47 + that any problems introduced by others will not reflect on the original 48 + authors' reputations. 49 + 50 + Finally, any free program is threatened constantly by software 51 + patents. We wish to avoid the danger that redistributors of a free 52 + program will individually obtain patent licenses, in effect making the 53 + program proprietary. To prevent this, we have made it clear that any 54 + patent must be licensed for everyone's free use or not licensed at all. 55 + 56 + The precise terms and conditions for copying, distribution and 57 + modification follow. 58 + 59 + GNU GENERAL PUBLIC LICENSE 60 + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 + 62 + 0. This License applies to any program or other work which contains 63 + a notice placed by the copyright holder saying it may be distributed 64 + under the terms of this General Public License. The "Program", below, 65 + refers to any such program or work, and a "work based on the Program" 66 + means either the Program or any derivative work under copyright law: 67 + that is to say, a work containing the Program or a portion of it, 68 + either verbatim or with modifications and/or translated into another 69 + language. (Hereinafter, translation is included without limitation in 70 + the term "modification".) Each licensee is addressed as "you". 71 + 72 + Activities other than copying, distribution and modification are not 73 + covered by this License; they are outside its scope. The act of 74 + running the Program is not restricted, and the output from the Program 75 + is covered only if its contents constitute a work based on the 76 + Program (independent of having been made by running the Program). 77 + Whether that is true depends on what the Program does. 78 + 79 + 1. You may copy and distribute verbatim copies of the Program's 80 + source code as you receive it, in any medium, provided that you 81 + conspicuously and appropriately publish on each copy an appropriate 82 + copyright notice and disclaimer of warranty; keep intact all the 83 + notices that refer to this License and to the absence of any warranty; 84 + and give any other recipients of the Program a copy of this License 85 + along with the Program. 86 + 87 + You may charge a fee for the physical act of transferring a copy, and 88 + you may at your option offer warranty protection in exchange for a fee. 89 + 90 + 2. You may modify your copy or copies of the Program or any portion 91 + of it, thus forming a work based on the Program, and copy and 92 + distribute such modifications or work under the terms of Section 1 93 + above, provided that you also meet all of these conditions: 94 + 95 + a) You must cause the modified files to carry prominent notices 96 + stating that you changed the files and the date of any change. 97 + 98 + b) You must cause any work that you distribute or publish, that in 99 + whole or in part contains or is derived from the Program or any 100 + part thereof, to be licensed as a whole at no charge to all third 101 + parties under the terms of this License. 102 + 103 + c) If the modified program normally reads commands interactively 104 + when run, you must cause it, when started running for such 105 + interactive use in the most ordinary way, to print or display an 106 + announcement including an appropriate copyright notice and a 107 + notice that there is no warranty (or else, saying that you provide 108 + a warranty) and that users may redistribute the program under 109 + these conditions, and telling the user how to view a copy of this 110 + License. (Exception: if the Program itself is interactive but 111 + does not normally print such an announcement, your work based on 112 + the Program is not required to print an announcement.) 113 + 114 + These requirements apply to the modified work as a whole. If 115 + identifiable sections of that work are not derived from the Program, 116 + and can be reasonably considered independent and separate works in 117 + themselves, then this License, and its terms, do not apply to those 118 + sections when you distribute them as separate works. But when you 119 + distribute the same sections as part of a whole which is a work based 120 + on the Program, the distribution of the whole must be on the terms of 121 + this License, whose permissions for other licensees extend to the 122 + entire whole, and thus to each and every part regardless of who wrote it. 123 + 124 + Thus, it is not the intent of this section to claim rights or contest 125 + your rights to work written entirely by you; rather, the intent is to 126 + exercise the right to control the distribution of derivative or 127 + collective works based on the Program. 128 + 129 + In addition, mere aggregation of another work not based on the Program 130 + with the Program (or with a work based on the Program) on a volume of 131 + a storage or distribution medium does not bring the other work under 132 + the scope of this License. 133 + 134 + 3. You may copy and distribute the Program (or a work based on it, 135 + under Section 2) in object code or executable form under the terms of 136 + Sections 1 and 2 above provided that you also do one of the following: 137 + 138 + a) Accompany it with the complete corresponding machine-readable 139 + source code, which must be distributed under the terms of Sections 140 + 1 and 2 above on a medium customarily used for software interchange; or, 141 + 142 + b) Accompany it with a written offer, valid for at least three 143 + years, to give any third party, for a charge no more than your 144 + cost of physically performing source distribution, a complete 145 + machine-readable copy of the corresponding source code, to be 146 + distributed under the terms of Sections 1 and 2 above on a medium 147 + customarily used for software interchange; or, 148 + 149 + c) Accompany it with the information you received as to the offer 150 + to distribute corresponding source code. (This alternative is 151 + allowed only for noncommercial distribution and only if you 152 + received the program in object code or executable form with such 153 + an offer, in accord with Subsection b above.) 154 + 155 + The source code for a work means the preferred form of the work for 156 + making modifications to it. For an executable work, complete source 157 + code means all the source code for all modules it contains, plus any 158 + associated interface definition files, plus the scripts used to 159 + control compilation and installation of the executable. However, as a 160 + special exception, the source code distributed need not include 161 + anything that is normally distributed (in either source or binary 162 + form) with the major components (compiler, kernel, and so on) of the 163 + operating system on which the executable runs, unless that component 164 + itself accompanies the executable. 165 + 166 + If distribution of executable or object code is made by offering 167 + access to copy from a designated place, then offering equivalent 168 + access to copy the source code from the same place counts as 169 + distribution of the source code, even though third parties are not 170 + compelled to copy the source along with the object code. 171 + 172 + 4. You may not copy, modify, sublicense, or distribute the Program 173 + except as expressly provided under this License. Any attempt 174 + otherwise to copy, modify, sublicense or distribute the Program is 175 + void, and will automatically terminate your rights under this License. 176 + However, parties who have received copies, or rights, from you under 177 + this License will not have their licenses terminated so long as such 178 + parties remain in full compliance. 179 + 180 + 5. You are not required to accept this License, since you have not 181 + signed it. However, nothing else grants you permission to modify or 182 + distribute the Program or its derivative works. These actions are 183 + prohibited by law if you do not accept this License. Therefore, by 184 + modifying or distributing the Program (or any work based on the 185 + Program), you indicate your acceptance of this License to do so, and 186 + all its terms and conditions for copying, distributing or modifying 187 + the Program or works based on it. 188 + 189 + 6. Each time you redistribute the Program (or any work based on the 190 + Program), the recipient automatically receives a license from the 191 + original licensor to copy, distribute or modify the Program subject to 192 + these terms and conditions. You may not impose any further 193 + restrictions on the recipients' exercise of the rights granted herein. 194 + You are not responsible for enforcing compliance by third parties to 195 + this License. 196 + 197 + 7. If, as a consequence of a court judgment or allegation of patent 198 + infringement or for any other reason (not limited to patent issues), 199 + conditions are imposed on you (whether by court order, agreement or 200 + otherwise) that contradict the conditions of this License, they do not 201 + excuse you from the conditions of this License. If you cannot 202 + distribute so as to satisfy simultaneously your obligations under this 203 + License and any other pertinent obligations, then as a consequence you 204 + may not distribute the Program at all. For example, if a patent 205 + license would not permit royalty-free redistribution of the Program by 206 + all those who receive copies directly or indirectly through you, then 207 + the only way you could satisfy both it and this License would be to 208 + refrain entirely from distribution of the Program. 209 + 210 + If any portion of this section is held invalid or unenforceable under 211 + any particular circumstance, the balance of the section is intended to 212 + apply and the section as a whole is intended to apply in other 213 + circumstances. 214 + 215 + It is not the purpose of this section to induce you to infringe any 216 + patents or other property right claims or to contest validity of any 217 + such claims; this section has the sole purpose of protecting the 218 + integrity of the free software distribution system, which is 219 + implemented by public license practices. Many people have made 220 + generous contributions to the wide range of software distributed 221 + through that system in reliance on consistent application of that 222 + system; it is up to the author/donor to decide if he or she is willing 223 + to distribute software through any other system and a licensee cannot 224 + impose that choice. 225 + 226 + This section is intended to make thoroughly clear what is believed to 227 + be a consequence of the rest of this License. 228 + 229 + 8. If the distribution and/or use of the Program is restricted in 230 + certain countries either by patents or by copyrighted interfaces, the 231 + original copyright holder who places the Program under this License 232 + may add an explicit geographical distribution limitation excluding 233 + those countries, so that distribution is permitted only in or among 234 + countries not thus excluded. In such case, this License incorporates 235 + the limitation as if written in the body of this License. 236 + 237 + 9. The Free Software Foundation may publish revised and/or new versions 238 + of the General Public License from time to time. Such new versions will 239 + be similar in spirit to the present version, but may differ in detail to 240 + address new problems or concerns. 241 + 242 + Each version is given a distinguishing version number. If the Program 243 + specifies a version number of this License which applies to it and "any 244 + later version", you have the option of following the terms and conditions 245 + either of that version or of any later version published by the Free 246 + Software Foundation. If the Program does not specify a version number of 247 + this License, you may choose any version ever published by the Free Software 248 + Foundation. 249 + 250 + 10. If you wish to incorporate parts of the Program into other free 251 + programs whose distribution conditions are different, write to the author 252 + to ask for permission. For software which is copyrighted by the Free 253 + Software Foundation, write to the Free Software Foundation; we sometimes 254 + make exceptions for this. Our decision will be guided by the two goals 255 + of preserving the free status of all derivatives of our free software and 256 + of promoting the sharing and reuse of software generally. 257 + 258 + NO WARRANTY 259 + 260 + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 + REPAIR OR CORRECTION. 269 + 270 + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 + POSSIBILITY OF SUCH DAMAGES. 279 + 280 + END OF TERMS AND CONDITIONS 281 + 282 + How to Apply These Terms to Your New Programs 283 + 284 + If you develop a new program, and you want it to be of the greatest 285 + possible use to the public, the best way to achieve this is to make it 286 + free software which everyone can redistribute and change under these terms. 287 + 288 + To do so, attach the following notices to the program. It is safest 289 + to attach them to the start of each source file to most effectively 290 + convey the exclusion of warranty; and each file should have at least 291 + the "copyright" line and a pointer to where the full notice is found. 292 + 293 + <one line to give the program's name and a brief idea of what it does.> 294 + Copyright (C) <year> <name of author> 295 + 296 + This program is free software; you can redistribute it and/or modify 297 + it under the terms of the GNU General Public License as published by 298 + the Free Software Foundation; either version 2 of the License, or 299 + (at your option) any later version. 300 + 301 + This program is distributed in the hope that it will be useful, 302 + but WITHOUT ANY WARRANTY; without even the implied warranty of 303 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 + GNU General Public License for more details. 305 + 306 + You should have received a copy of the GNU General Public License along 307 + with this program; if not, see <https://www.gnu.org/licenses/>. 308 + 309 + Also add information on how to contact you by electronic and paper mail. 310 + 311 + If the program is interactive, make it output a short notice like this 312 + when it starts in an interactive mode: 313 + 314 + Gnomovision version 69, Copyright (C) year name of author 315 + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 316 + This is free software, and you are welcome to redistribute it 317 + under certain conditions; type `show c' for details. 318 + 319 + The hypothetical commands `show w' and `show c' should show the appropriate 320 + parts of the General Public License. Of course, the commands you use may 321 + be called something other than `show w' and `show c'; they could even be 322 + mouse-clicks or menu items--whatever suits your program. 323 + 324 + You should also get your employer (if you work as a programmer) or your 325 + school, if any, to sign a "copyright disclaimer" for the program, if 326 + necessary. Here is a sample; alter the names: 327 + 328 + Yoyodyne, Inc., hereby disclaims all copyright interest in the program 329 + `Gnomovision' (which makes passes at compilers) written by James Hacker. 330 + 331 + <signature of Moe Ghoul>, 1 April 1989 332 + Moe Ghoul, President of Vice 333 + 334 + This General Public License does not permit incorporating your program into 335 + proprietary programs. If your program is a subroutine library, you may 336 + consider it more useful to permit linking proprietary applications with the 337 + library. If this is what you want to do, use the GNU Lesser General 338 + Public License instead of this License.
+122 -33
cli/README.md
··· 1 1 # atradio 2 2 3 - `atradio.fm` in your terminal — a synthwave TUI radio player on the AT Protocol. 3 + `atradio.fm` in your terminal — a TUI radio player on the AT Protocol. 4 4 5 5 A native Rust client for [atradio.fm](https://atradio.fm): browse trending / 6 - popular stations, fuzzy-search the whole radio-browser directory, play live 7 - streams with a full Rockbox DSP/equalizer chain, and — when signed in — 8 - favorite stations and post comments to your own PDS. 6 + popular / recently-played stations, fuzzy-search the whole radio-browser 7 + directory, play live streams with a full Rockbox DSP/equalizer chain, and — 8 + when signed in — favorite stations, add your own, and post comments to your PDS. 9 + 10 + ![atradio](preview.png) 11 + 12 + ## Install 13 + 14 + Prebuilt release tarballs, `.deb`, and `.rpm` packages are attached to every 15 + [GitHub release](https://github.com/tsirysndr/atradio.fm/releases), named 16 + `atradio-<version>-<os>-<arch>.tar.gz` (`macos-amd64`, `macos-aarch64`, 17 + `linux-amd64`, `linux-aarch64`) — each contains the binary, README, and LICENSE. 18 + 19 + ### macOS / Linux — Homebrew 20 + 21 + ```bash 22 + brew install tsirysndr/tap/atradio 23 + ``` 24 + 25 + ### Linux — Debian / Ubuntu 26 + 27 + Direct `.deb`: 28 + 29 + ```bash 30 + # amd64 31 + curl -LO https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio_0.1.0_amd64.deb 32 + sudo apt install ./atradio_0.1.0_amd64.deb 33 + 34 + # arm64 (Raspberry Pi 4/5, Apple-silicon VM, …) 35 + curl -LO https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio_0.1.0_arm64.deb 36 + sudo apt install ./atradio_0.1.0_arm64.deb 37 + ``` 38 + 39 + Or via the Gemfury apt repo (auto-updates with `apt upgrade`): 40 + 41 + ```bash 42 + echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" \ 43 + | sudo tee /etc/apt/sources.list.d/tsiry.list 44 + sudo apt update && sudo apt install atradio 45 + ``` 46 + 47 + ### Linux — Fedora / RHEL / openSUSE 48 + 49 + Direct `.rpm`: 50 + 51 + ```bash 52 + sudo dnf install \ 53 + https://github.com/tsirysndr/atradio.fm/releases/latest/download/atradio-0.1.0-1.x86_64.rpm 54 + ``` 55 + 56 + Or via the Gemfury dnf/yum repo: 57 + 58 + ```bash 59 + sudo tee /etc/yum.repos.d/tsiry.repo <<'EOF' 60 + [tsiry] 61 + name=tsiry 62 + baseurl=https://yum.fury.io/tsiry/ 63 + enabled=1 64 + gpgcheck=0 65 + EOF 66 + sudo dnf install atradio 67 + ``` 68 + 69 + ### Nix 70 + 71 + ```bash 72 + # Optional: use the binary cache to skip building. 73 + cachix use tsirysndr 74 + 75 + # One-off run: 76 + nix run github:tsirysndr/atradio.fm 77 + 78 + # Install into your user profile: 79 + nix profile install github:tsirysndr/atradio.fm 80 + 81 + # Dev shell (rust toolchain + build deps): 82 + nix develop github:tsirysndr/atradio.fm 83 + ``` 9 84 10 - ## Build 85 + ### From source (Cargo) 11 86 12 87 ```bash 13 - cargo build --release # from the repo root, or from cli/ 14 - ./target/release/atradio # launch the TUI 88 + # Runtime/build deps: a C toolchain (for the Rockbox codecs) + ALSA on Linux. 89 + sudo apt-get install -y build-essential pkg-config libasound2-dev # Debian/Ubuntu 90 + 91 + cargo install --git https://github.com/tsirysndr/atradio.fm --bin atradio 92 + ``` 93 + 94 + ## Build (from a checkout) 95 + 96 + ```bash 97 + cd cli 98 + cargo build --release 99 + ./target/release/atradio # launch the TUI 15 100 ``` 16 101 17 102 Building compiles the vendored Rockbox codecs, so a C toolchain is required 18 - (clang/gcc). macOS uses CoreAudio; Linux needs ALSA dev headers. 103 + (clang/gcc). macOS uses CoreAudio; Linux needs ALSA dev headers 104 + (`libasound2-dev`). 19 105 20 106 > **License note:** this crate links `rockbox-playback` (GPL-2.0-or-later), so 21 107 > the compiled `atradio` binary is GPL-2.0-or-later. ··· 36 122 37 123 ## Signing in 38 124 39 - Reads / posts to the AppView are public; **favoriting and commenting require a 40 - session.** Two ways to authenticate: 125 + Reads to the AppView are public; **favoriting, commenting, adding stations, and 126 + appearing in recently-played require a session.** Two ways to authenticate: 41 127 42 128 - **App password** — set env vars, then `atradio login`: 43 129 ```bash 44 130 export ATPROTO_IDENTIFIER="you.bsky.social" 45 131 export ATPROTO_APP_PASSWORD="xxxx-xxxx-xxxx-xxxx" 46 132 ``` 47 - - **OAuth** — `atradio login --oauth you.bsky.social` opens your PDS in the 48 - browser. 133 + - **OAuth** — `atradio login --oauth you.bsky.social`, or press `s` in the TUI 134 + to open the sign-in modal, which completes the flow in your browser. 49 135 50 - The session + a small profile cache are stored under 51 - `~/.config/atradio/` (also `settings.toml` for volume + DSP). 136 + The session + a small profile cache are stored under `~/.config/atradio/` 137 + (also `settings.toml` for volume + DSP). 52 138 53 139 ## Keybindings (TUI) 54 140 55 - | Key | Action | 56 - | --- | --- | 57 - | `↑`/`↓` `j`/`k` | move selection | 58 - | `←`/`→` `Tab` | switch list / home tab | 59 - | `Enter` | play the selected station | 60 - | `Space` | play / pause | 61 - | `+` / `-` | volume up / down (or adjust the focused DSP value) | 62 - | `m` | mute | 63 - | `/` | fuzzy station search (fzf-style) | 64 - | `f` | favorite the selected/current station | 65 - | `c` / `a` | comments / add a comment | 66 - | `n` | notifications | 67 - | `e` | equalizer & DSP settings | 68 - | `h` · `?` | home · help | 69 - | `q` / `Esc` | quit / close overlay | 141 + | Key | Action | 142 + | --------------- | -------------------------------------------------------- | 143 + | `↑`/`↓` `j`/`k` | move selection | 144 + | `←`/`→` `Tab` | switch home tab | 145 + | `1` … `5` | tabs: Trending / Popular / Recent / Favorites / Yours | 146 + | `Enter` | play the selected station | 147 + | `Space` | play / pause · `m` mute · `+`/`-` volume (or adjust DSP) | 148 + | `/` | fuzzy station search | 149 + | `f` | favorite the selected/current station | 150 + | `A` | add a custom station (when signed in) | 151 + | `c` / `a` | comments / add a comment | 152 + | `n` | notifications | 153 + | `p` | your profile (with playable recently-played) | 154 + | `e` | equalizer & DSP settings | 155 + | `s` | sign in (OAuth) / sign out | 156 + | `h` · `?` | home · help | 157 + | `q` / `Esc` | quit / close overlay | 70 158 71 159 ## Equalizer & DSP 72 160 73 - Press `e` for the full Rockbox chain, mirroring the web app: a 10-band 74 - equalizer, bass/treble tone, crossfeed, perceptual bass, Haas surround, a 75 - compressor, and channel mode / stereo width. Changes apply live and persist to 76 - `settings.toml`. 161 + Press `e` for the full Rockbox chain: a 10-band equalizer, bass/treble tone, 162 + crossfeed, perceptual bass, Haas surround, a compressor, and channel mode / 163 + stereo width. Changes apply live and persist to `settings.toml`. DSP stays 164 + **local** — the native EQ bands (32 Hz–16 kHz) differ from the web build's, so 165 + they are not synced to your PDS. 77 166 78 167 ## Platform notes 79 168
+14
cli/dist/debian/amd64/DEBIAN/control
··· 1 + Package: atradio 2 + Version: 0.1.0 3 + Section: sound 4 + Priority: optional 5 + Architecture: amd64 6 + Maintainer: Tsiry Sandratraina <tsiry.sndr@rocksky.app> 7 + Homepage: https://github.com/tsirysndr/atradio.fm 8 + Depends: libc6, libasound2 9 + Description: A TUI radio player on the AT Protocol 10 + atradio brings atradio.fm to your terminal: browse trending / popular 11 + stations, fuzzy-search the whole radio-browser directory, play live 12 + streams with a full Rockbox DSP/equalizer chain, and — when signed in — 13 + favorite stations and post comments to your own PDS. On Linux the player 14 + is exposed over MPRIS for media-key and desktop integration.
+14
cli/dist/debian/arm64/DEBIAN/control
··· 1 + Package: atradio 2 + Version: 0.1.0 3 + Section: sound 4 + Priority: optional 5 + Architecture: arm64 6 + Maintainer: Tsiry Sandratraina <tsiry.sndr@rocksky.app> 7 + Homepage: https://github.com/tsirysndr/atradio.fm 8 + Depends: libc6, libasound2 9 + Description: A TUI radio player on the AT Protocol 10 + atradio brings atradio.fm to your terminal: browse trending / popular 11 + stations, fuzzy-search the whole radio-browser directory, play live 12 + streams with a full Rockbox DSP/equalizer chain, and — when signed in — 13 + favorite stations and post comments to your own PDS. On Linux the player 14 + is exposed over MPRIS for media-key and desktop integration.
+41
cli/dist/homebrew/atradio.rb
··· 1 + # Template formula for the `tsirysndr/homebrew-tap` repo. 2 + # Copy this to your tap as `Formula/atradio.rb` and fill in the sha256 values 3 + # (from the `*.tar.gz.sha256` files attached to each GitHub release), or wire an 4 + # action that bumps `version` + sha256 on every release. 5 + class Atradio < Formula 6 + desc "A TUI radio player on the AT Protocol" 7 + homepage "https://github.com/tsirysndr/atradio.fm" 8 + version "0.1.0" 9 + license "GPL-2.0-or-later" 10 + 11 + on_macos do 12 + on_arm do 13 + url "https://github.com/tsirysndr/atradio.fm/releases/download/v#{version}/atradio-v#{version}-macos-aarch64.tar.gz" 14 + sha256 "REPLACE_WITH_MACOS_AARCH64_SHA256" 15 + end 16 + on_intel do 17 + url "https://github.com/tsirysndr/atradio.fm/releases/download/v#{version}/atradio-v#{version}-macos-amd64.tar.gz" 18 + sha256 "REPLACE_WITH_MACOS_AMD64_SHA256" 19 + end 20 + end 21 + 22 + on_linux do 23 + depends_on "alsa-lib" 24 + on_arm do 25 + url "https://github.com/tsirysndr/atradio.fm/releases/download/v#{version}/atradio-v#{version}-linux-aarch64.tar.gz" 26 + sha256 "REPLACE_WITH_LINUX_AARCH64_SHA256" 27 + end 28 + on_intel do 29 + url "https://github.com/tsirysndr/atradio.fm/releases/download/v#{version}/atradio-v#{version}-linux-amd64.tar.gz" 30 + sha256 "REPLACE_WITH_LINUX_AMD64_SHA256" 31 + end 32 + end 33 + 34 + def install 35 + bin.install "atradio" 36 + end 37 + 38 + test do 39 + assert_match "atradio", shell_output("#{bin}/atradio --version") 40 + end 41 + end
+36
cli/dist/rpm/amd64/atradio.spec
··· 1 + Name: atradio 2 + Version: 0.1.0 3 + Release: 1%{?dist} 4 + Summary: A TUI radio player on the AT Protocol 5 + 6 + License: GPLv2+ 7 + URL: https://github.com/tsirysndr/atradio.fm 8 + 9 + BuildArch: x86_64 10 + 11 + Requires: glibc, alsa-lib 12 + 13 + %description 14 + atradio brings atradio.fm to your terminal: browse trending / popular stations, 15 + fuzzy-search the whole radio-browser directory, play live streams with a full 16 + Rockbox DSP/equalizer chain, and — when signed in — favorite stations and post 17 + comments to your own PDS. On Linux the player is exposed over MPRIS for 18 + media-key and desktop integration. 19 + 20 + %prep 21 + # Nothing to prep — the binary is prebuilt. 22 + 23 + %build 24 + # Nothing to build — the binary is prebuilt. 25 + 26 + %install 27 + mkdir -p %{buildroot}/usr/local/bin 28 + cp -r %{_sourcedir}/amd64/usr %{buildroot}/ 29 + 30 + %files 31 + /usr/local/bin/atradio 32 + 33 + %post 34 + if [ "$1" -eq 1 ]; then 35 + echo "atradio: installed. Launch it with: atradio" 36 + fi
+116
cli/flake.lock
··· 1 + { 2 + "nodes": { 3 + "advisory-db": { 4 + "flake": false, 5 + "locked": { 6 + "lastModified": 1783963901, 7 + "narHash": "sha256-J/jP23jsQScKQDS2Yi2QzyvjZwLs/F3VE2oPxXL13Zo=", 8 + "owner": "rustsec", 9 + "repo": "advisory-db", 10 + "rev": "9f3e138091487e69144f536d36976e427a7a3307", 11 + "type": "github" 12 + }, 13 + "original": { 14 + "owner": "rustsec", 15 + "repo": "advisory-db", 16 + "type": "github" 17 + } 18 + }, 19 + "crane": { 20 + "locked": { 21 + "lastModified": 1784172867, 22 + "narHash": "sha256-hzzeOgTP0MXMvg4djVwysTDvvUXIpsUCgsA0wPMF/Qk=", 23 + "owner": "ipetkov", 24 + "repo": "crane", 25 + "rev": "8833b7dc3c7426ce1110ed6fed31b6f63d74f2dc", 26 + "type": "github" 27 + }, 28 + "original": { 29 + "owner": "ipetkov", 30 + "repo": "crane", 31 + "type": "github" 32 + } 33 + }, 34 + "fenix": { 35 + "inputs": { 36 + "nixpkgs": [ 37 + "nixpkgs" 38 + ], 39 + "rust-analyzer-src": [] 40 + }, 41 + "locked": { 42 + "lastModified": 1784190100, 43 + "narHash": "sha256-cE4wAMa3MDhnx+mH5Y72tM/VTSh5cS5S2q0w56XurJU=", 44 + "owner": "nix-community", 45 + "repo": "fenix", 46 + "rev": "010b0647f8ebce9cd9e185773a7f9e422af1154a", 47 + "type": "github" 48 + }, 49 + "original": { 50 + "owner": "nix-community", 51 + "repo": "fenix", 52 + "type": "github" 53 + } 54 + }, 55 + "flake-utils": { 56 + "inputs": { 57 + "systems": "systems" 58 + }, 59 + "locked": { 60 + "lastModified": 1731533236, 61 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 62 + "owner": "numtide", 63 + "repo": "flake-utils", 64 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 65 + "type": "github" 66 + }, 67 + "original": { 68 + "owner": "numtide", 69 + "repo": "flake-utils", 70 + "type": "github" 71 + } 72 + }, 73 + "nixpkgs": { 74 + "locked": { 75 + "lastModified": 1784120854, 76 + "narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=", 77 + "owner": "NixOS", 78 + "repo": "nixpkgs", 79 + "rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46", 80 + "type": "github" 81 + }, 82 + "original": { 83 + "owner": "NixOS", 84 + "ref": "nixos-unstable", 85 + "repo": "nixpkgs", 86 + "type": "github" 87 + } 88 + }, 89 + "root": { 90 + "inputs": { 91 + "advisory-db": "advisory-db", 92 + "crane": "crane", 93 + "fenix": "fenix", 94 + "flake-utils": "flake-utils", 95 + "nixpkgs": "nixpkgs" 96 + } 97 + }, 98 + "systems": { 99 + "locked": { 100 + "lastModified": 1681028828, 101 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 102 + "owner": "nix-systems", 103 + "repo": "default", 104 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 105 + "type": "github" 106 + }, 107 + "original": { 108 + "owner": "nix-systems", 109 + "repo": "default", 110 + "type": "github" 111 + } 112 + } 113 + }, 114 + "root": "root", 115 + "version": 7 116 + }
+156
cli/flake.nix
··· 1 + { 2 + description = "atradio — a TUI radio player on the AT Protocol"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + 7 + # Current crane doesn't expose a `nixpkgs` input, so we don't follow it. 8 + crane.url = "github:ipetkov/crane"; 9 + 10 + fenix = { 11 + url = "github:nix-community/fenix"; 12 + inputs.nixpkgs.follows = "nixpkgs"; 13 + inputs.rust-analyzer-src.follows = ""; 14 + }; 15 + 16 + flake-utils.url = "github:numtide/flake-utils"; 17 + 18 + advisory-db = { 19 + url = "github:rustsec/advisory-db"; 20 + flake = false; 21 + }; 22 + }; 23 + 24 + outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }: 25 + flake-utils.lib.eachDefaultSystem (system: 26 + let 27 + pkgs = import nixpkgs { inherit system; }; 28 + 29 + inherit (pkgs) lib; 30 + 31 + craneLib = crane.mkLib pkgs; 32 + 33 + src = craneLib.cleanCargoSource ./.; 34 + 35 + # atradio uses rustls end-to-end, so no openssl. Unlike the web build it 36 + # has NO runtime binary dependency (rockbox-playback decodes in-process), 37 + # so there is no wrapProgram/PATH shim — the binary stands alone. 38 + commonArgs = { 39 + inherit src; 40 + 41 + pname = "atradio"; 42 + version = "0.1.0"; 43 + strictDeps = true; 44 + 45 + # rockbox-playback pulls in rockbox-codecs + rockbox-dsp, whose build 46 + # scripts compile Rockbox's C codec/DSP sources with the `cc` crate — 47 + # so a C compiler must be on PATH. `stdenv.cc` is that toolchain 48 + # (clang on Darwin, gcc on Linux). 49 + nativeBuildInputs = [ 50 + pkgs.pkg-config 51 + pkgs.stdenv.cc 52 + ] ++ lib.optionals pkgs.stdenv.isDarwin [ 53 + # coreaudio-sys generates its CoreAudio bindings with bindgen at 54 + # build time; bindgenHook provides libclang + the Nix Apple SDK. 55 + pkgs.rustPlatform.bindgenHook 56 + ]; 57 + 58 + buildInputs = lib.optionals pkgs.stdenv.isDarwin [ 59 + pkgs.libiconv 60 + ] ++ lib.optionals pkgs.stdenv.isLinux [ 61 + # cpal links against ALSA on Linux for the audio output path. 62 + # MPRIS needs no dbus dev lib: zbus (via mpris-server) is pure Rust. 63 + pkgs.alsa-lib 64 + ]; 65 + 66 + # Single bin target. 67 + cargoExtraArgs = "--locked --bin atradio"; 68 + }; 69 + 70 + craneLibLLvmTools = craneLib.overrideToolchain 71 + (fenix.packages.${system}.complete.withComponents [ 72 + "cargo" 73 + "llvm-tools" 74 + "rustc" 75 + ]); 76 + 77 + # Cache the dependency graph separately from the crate source. 78 + cargoArtifacts = craneLib.buildDepsOnly commonArgs; 79 + 80 + atradio = craneLib.buildPackage (commonArgs // { 81 + inherit cargoArtifacts; 82 + doCheck = false; 83 + 84 + meta = { 85 + description = "A TUI radio player on the AT Protocol"; 86 + homepage = "https://github.com/tsirysndr/atradio.fm"; 87 + # Linking rockbox-playback (GPL-2.0) makes the binary GPL-2.0+. 88 + license = lib.licenses.gpl2Plus; 89 + mainProgram = "atradio"; 90 + platforms = lib.platforms.unix; 91 + }; 92 + }); 93 + 94 + in 95 + { 96 + checks = { 97 + inherit atradio; 98 + 99 + atradio-clippy = craneLib.cargoClippy (commonArgs // { 100 + inherit cargoArtifacts; 101 + cargoClippyExtraArgs = "--all-targets -- --deny warnings"; 102 + }); 103 + 104 + atradio-fmt = craneLib.cargoFmt { 105 + inherit src; 106 + }; 107 + 108 + atradio-audit = craneLib.cargoAudit { 109 + inherit src advisory-db; 110 + }; 111 + }; 112 + 113 + packages = { 114 + default = atradio; 115 + atradio = atradio; 116 + 117 + atradio-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // { 118 + inherit cargoArtifacts; 119 + }); 120 + }; 121 + 122 + apps.default = flake-utils.lib.mkApp { 123 + drv = atradio; 124 + name = "atradio"; 125 + }; 126 + 127 + devShells.default = pkgs.mkShell { 128 + inputsFrom = builtins.attrValues self.checks.${system}; 129 + 130 + # Build-time tools. pkg-config resolves libasound for cpal's build.rs 131 + # on Linux; stdenv.cc supplies the C compiler the rockbox-codecs / 132 + # rockbox-dsp build scripts need for Rockbox's C sources. 133 + nativeBuildInputs = with pkgs; [ 134 + cargo 135 + rustc 136 + rustfmt 137 + clippy 138 + rust-analyzer 139 + pkg-config 140 + stdenv.cc 141 + ]; 142 + 143 + # Link-time libraries. pkg-config only picks up `.pc` files from 144 + # buildInputs, so alsa-lib MUST live here for cpal → ALSA to resolve. 145 + buildInputs = with pkgs; lib.optionals stdenv.isDarwin [ 146 + libiconv 147 + ] ++ lib.optionals stdenv.isLinux [ 148 + alsa-lib 149 + ]; 150 + 151 + shellHook = '' 152 + echo "◈ atradio dev shell — $(cargo --version)" 153 + ''; 154 + }; 155 + }); 156 + }
cli/preview.png

This is a binary file and will not be displayed.

+139
flake.lock
··· 1 + { 2 + "nodes": { 3 + "advisory-db": { 4 + "flake": false, 5 + "locked": { 6 + "lastModified": 1783963901, 7 + "narHash": "sha256-J/jP23jsQScKQDS2Yi2QzyvjZwLs/F3VE2oPxXL13Zo=", 8 + "owner": "rustsec", 9 + "repo": "advisory-db", 10 + "rev": "9f3e138091487e69144f536d36976e427a7a3307", 11 + "type": "github" 12 + }, 13 + "original": { 14 + "owner": "rustsec", 15 + "repo": "advisory-db", 16 + "type": "github" 17 + } 18 + }, 19 + "cli": { 20 + "inputs": { 21 + "advisory-db": "advisory-db", 22 + "crane": "crane", 23 + "fenix": "fenix", 24 + "flake-utils": [ 25 + "flake-utils" 26 + ], 27 + "nixpkgs": [ 28 + "nixpkgs" 29 + ] 30 + }, 31 + "locked": { 32 + "path": "./cli", 33 + "type": "path" 34 + }, 35 + "original": { 36 + "path": "./cli", 37 + "type": "path" 38 + }, 39 + "parent": [] 40 + }, 41 + "crane": { 42 + "locked": { 43 + "lastModified": 1784172867, 44 + "narHash": "sha256-hzzeOgTP0MXMvg4djVwysTDvvUXIpsUCgsA0wPMF/Qk=", 45 + "owner": "ipetkov", 46 + "repo": "crane", 47 + "rev": "8833b7dc3c7426ce1110ed6fed31b6f63d74f2dc", 48 + "type": "github" 49 + }, 50 + "original": { 51 + "owner": "ipetkov", 52 + "repo": "crane", 53 + "type": "github" 54 + } 55 + }, 56 + "fenix": { 57 + "inputs": { 58 + "nixpkgs": [ 59 + "cli", 60 + "nixpkgs" 61 + ], 62 + "rust-analyzer-src": [ 63 + "cli" 64 + ] 65 + }, 66 + "locked": { 67 + "lastModified": 1784190100, 68 + "narHash": "sha256-cE4wAMa3MDhnx+mH5Y72tM/VTSh5cS5S2q0w56XurJU=", 69 + "owner": "nix-community", 70 + "repo": "fenix", 71 + "rev": "010b0647f8ebce9cd9e185773a7f9e422af1154a", 72 + "type": "github" 73 + }, 74 + "original": { 75 + "owner": "nix-community", 76 + "repo": "fenix", 77 + "type": "github" 78 + } 79 + }, 80 + "flake-utils": { 81 + "inputs": { 82 + "systems": "systems" 83 + }, 84 + "locked": { 85 + "lastModified": 1731533236, 86 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 87 + "owner": "numtide", 88 + "repo": "flake-utils", 89 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 90 + "type": "github" 91 + }, 92 + "original": { 93 + "owner": "numtide", 94 + "repo": "flake-utils", 95 + "type": "github" 96 + } 97 + }, 98 + "nixpkgs": { 99 + "locked": { 100 + "lastModified": 1784120854, 101 + "narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=", 102 + "owner": "NixOS", 103 + "repo": "nixpkgs", 104 + "rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46", 105 + "type": "github" 106 + }, 107 + "original": { 108 + "owner": "NixOS", 109 + "ref": "nixos-unstable", 110 + "repo": "nixpkgs", 111 + "type": "github" 112 + } 113 + }, 114 + "root": { 115 + "inputs": { 116 + "cli": "cli", 117 + "flake-utils": "flake-utils", 118 + "nixpkgs": "nixpkgs" 119 + } 120 + }, 121 + "systems": { 122 + "locked": { 123 + "lastModified": 1681028828, 124 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 125 + "owner": "nix-systems", 126 + "repo": "default", 127 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 128 + "type": "github" 129 + }, 130 + "original": { 131 + "owner": "nix-systems", 132 + "repo": "default", 133 + "type": "github" 134 + } 135 + } 136 + }, 137 + "root": "root", 138 + "version": 7 139 + }
+32
flake.nix
··· 1 + { 2 + description = "atradio.fm — the CLI/TUI lives in ./cli (the default package)"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-utils.url = "github:numtide/flake-utils"; 7 + 8 + # The CLI is a self-contained sub-flake; the root re-exports it so 9 + # `nix build`, `nix run`, and `nix profile install github:tsirysndr/atradio.fm` 10 + # all resolve to the CLI. Add more sub-flakes here as the repo grows. 11 + cli = { 12 + url = "path:./cli"; 13 + inputs.nixpkgs.follows = "nixpkgs"; 14 + inputs.flake-utils.follows = "flake-utils"; 15 + }; 16 + }; 17 + 18 + outputs = { self, nixpkgs, flake-utils, cli, ... }: 19 + flake-utils.lib.eachDefaultSystem (system: { 20 + # Root default = the CLI (only package for now). 21 + packages = { 22 + default = cli.packages.${system}.default; 23 + atradio = cli.packages.${system}.default; 24 + }; 25 + 26 + apps.default = cli.apps.${system}.default; 27 + 28 + devShells.default = cli.devShells.${system}.default; 29 + 30 + checks = cli.checks.${system}; 31 + }); 32 + }