29 Commits

Author SHA1 Message Date
0db9b6a795 Archival Notice 2025-01-17 14:40:36 -06:00
3b8e27d9a4 Bump rust version to 1.79 to support absolute paths feature [stabilized] 2025-01-17 14:35:13 -06:00
31b5e6ee8b Setup basic parsing, 'proper' implementation 2025-01-17 14:10:34 -06:00
20c863e660 Use keep-a-changelog style for CHANGELOG.md 2024-10-08 15:31:04 -05:00
0d0fe8a9e5 Use GH_RELEASE_TOKEN for build 'Release' step 2024-10-08 00:06:01 -05:00
3fb288f404 Updated changelog 2024-10-07 15:12:15 -05:00
8e33506f61 Improve paths-ignore, disable paths-ignore for build 'push' trigger 2024-10-07 15:04:39 -05:00
48b0101dab Disable hooks from changing repository language statistics 2024-10-07 15:00:46 -05:00
f583dac944 Update version to v0.1.6 2024-10-07 14:49:17 -05:00
dbfad44a51 Add schedule for workflow test 2024-10-07 14:43:02 -05:00
a636c6301a Mark TODO for pre-commit hook 2024-10-07 14:42:50 -05:00
30d135a4dc rename other workflows to lowercase 2024-10-07 14:32:22 -05:00
62fb74a153 Add testing badge, update changelog 2024-10-07 14:28:02 -05:00
c3e15fd9a5 Disable workflow_dispatch fail_fast input 2024-10-07 14:27:51 -05:00
0cc92b8978 Add optional artifact step for ubuntu build, fix fail_fast evaluation for non-dispatch 2024-10-07 14:25:15 -05:00
2e15d81dc9 Remove x86_64-unknown-linux-musl target runner detail, use defaults 2024-10-07 14:12:25 -05:00
28faf71583 Fix typo in name, simplify fail-fast evaluation 2024-10-07 14:12:06 -05:00
9ef2aac601 Add testing workflow (every push) 2024-10-07 13:59:33 -05:00
7e7af5920b lowercase the build workflow name 2024-10-07 13:52:13 -05:00
978b0ab264 Add CHANGELOG.md 2024-10-07 13:44:00 -05:00
a4568b2e72 Update README.md 2024-10-07 13:38:56 -05:00
7eaea3e2f4 Switch build pipeline to MSRV 1.74 2024-10-07 13:38:47 -05:00
22606aad30 Fix pre-commit.sh hook 2024-10-07 13:28:47 -05:00
70fd9da886 Update to 0.1.5 2024-10-06 16:17:33 -05:00
50de07ef28 Fix version tag regex 2024-10-06 16:17:29 -05:00
f8da0a647b Fix RELEASE_VERSION not being available for ARCHIVE variable, separate local/global variable sets 2024-10-06 16:16:07 -05:00
acf81f6bf6 Add pre-commit, pre-push hooks formally 2024-10-06 16:10:46 -05:00
2866c2fe4a Disable unnecessary notice of key length 2024-10-06 16:08:19 -05:00
98fc3c9902 Fix pkg-url, default to tgz explicit, v0.1.4 2024-10-06 16:07:14 -05:00
15 changed files with 525 additions and 100 deletions

View File

@@ -1,6 +1,3 @@
[target.x86_64-unknown-linux-musl]
runner = "musl-gcc"
[profile.release]
opt-level = "z"
strip = true

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
.hooks/*.sh linguist-vendored

View File

@@ -1,4 +1,4 @@
name: Build
name: build
permissions:
contents: write
@@ -7,27 +7,29 @@ on:
workflow_dispatch:
inputs:
fail_fast:
description: 'Fail fast strategy'
description: "Use fail-fast strategy"
required: false
default: 'true'
default: "true"
verbose:
description: 'Verbose output'
description: "Verbose output"
required: false
default: 'false'
default: "false"
push:
tags:
- 'v*.*.*'
paths-ignore:
- README.md
- .gitignore
- LICENSE
- run.sh
tags:
- "v*.*.*"
# We don't filter by paths here, because we filter by tags, which means we're releasing. All releases should be built.
pull_request:
paths-ignore:
- .hooks/**
- CARGO_README.md
- README.md
- CHANGELOG.md
- INTEGRATION.md
- .gitignore
- LICENSE
- .gitattributes
- LICENSE*
- run.sh
- run.ps1
env:
CARGO_TERM_COLOR: always
@@ -36,7 +38,7 @@ jobs:
build:
strategy:
fail-fast: ${{ startsWith(github.ref, 'refs/tags/') || github.event.inputs.fail_fast == 'true' }}
matrix:
include:
- os: ubuntu-latest
@@ -72,7 +74,7 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
toolchain: 1.79
targets: ${{ matrix.target }}
- name: Cache Rust dependencies
@@ -86,7 +88,7 @@ jobs:
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
- name: Install Linker Tools
if: matrix.tools
run: |
@@ -112,14 +114,20 @@ jobs:
- name: Prepare Variables
shell: bash
run: |
echo "RELEASE_VERSION=$(grep '^version = ' Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')" >> $GITHUB_ENV
RELEASE_VERSION=$(grep '^version = ' Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')
ARCHIVE=spotify-quickauth-v$RELEASE_VERSION-${{ matrix.target }}
if ${{ contains(matrix.os, 'windows') }}; then
echo "ARCHIVE=spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.zip" >> $GITHUB_ENV
ARCHIVE=$ARCHIVE.zip
else
echo "ARCHIVE=spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.tar.gz" >> $GITHUB_ENV
ARCHIVE=$ARCHIVE.tar.gz
fi
echo "ARCHIVE_DIR=target/${{ matrix.target }}/release" >> $GITHUB_ENV
ARCHIVE_DIR=target/${{ matrix.target }}/release
# Export variables
echo "ARCHIVE_DIR=$ARCHIVE_DIR" >> $GITHUB_ENV
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV
- name: Archive
if: ${{ !contains(matrix.os, 'windows') }}
uses: TheDoctor0/zip-release@0.7.6
@@ -139,13 +147,13 @@ jobs:
directory: ${{ env.ARCHIVE_DIR }}/
path: |
spotify-quickauth.exe
- name: Install rsign2
if: startsWith(github.ref, 'refs/tags/')
uses: taiki-e/install-action@v2
with:
tool: rsign2
- name: Sign Archive
if: startsWith(github.ref, 'refs/tags/')
env:
@@ -153,16 +161,14 @@ jobs:
ARCHIVE_PATH: ${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}
shell: bash
run: |
echo "::notice:: length = $(echo -n $MINISIGN_KEY | wc -c)"
echo "::notice:: length = $(echo -n "$MINISIGN_KEY" | wc -c)"
echo "$MINISIGN_KEY" > minisign.key
ts=$(node -e 'console.log((new Date).toISOString())')
git=$(git rev-parse HEAD)
comment="gh=$GITHUB_REPOSITORY git=$git ts=$ts run=$GITHUB_RUN_ID"
rsign sign -W -s minisign.key -x "${{ env.ARCHIVE_PATH }}.sig" -t "$comment" "${{ env.ARCHIVE_PATH }}"
- name: Upload Artifact
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
@@ -176,6 +182,7 @@ jobs:
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
token: ${{ secrets.GH_RELEASE_TOKEN }}
files: |
${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}
${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}.sig
@@ -183,7 +190,7 @@ jobs:
- if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.target == 'x86_64-unknown-linux-musl' }}
run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
- name: "Publish"
if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.target == 'x86_64-unknown-linux-musl' }}
run: cargo publish --locked --allow-dirty
run: cargo publish --locked --allow-dirty

View File

@@ -1,4 +1,4 @@
name: Integration
name: integration
on:
workflow_dispatch:

View File

@@ -1,4 +1,4 @@
name: GitHub Pages
name: github-pages
permissions:
pages: write

98
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,98 @@
name: test
on:
workflow_dispatch:
push:
paths-ignore:
- .hooks/**
- CARGO_README.md
- README.md
- CHANGELOG.md
- INTEGRATION.md
- .gitignore
- .gitattributes
- LICENSE*
- run.sh
- run.ps1
pull_request:
paths-ignore:
- .hooks/**
- CARGO_README.md
- README.md
- CHANGELOG.md
- INTEGRATION.md
- .gitignore
- .gitattributes
- LICENSE*
- run.sh
- run.ps1
schedule:
- cron: "30 14 * * 1" # every Monday, 9:30 AM CDT
env:
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
tools: musl-tools
artifact: true
- os: macos-13
target: x86_64-apple-darwin
# - os: macos-latest
# target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
name: test-${{ matrix.os }}-${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.79
targets: ${{ matrix.target }}
- name: Install Linker Tools
if: matrix.tools
run: |
sudo apt-get update
sudo apt-get install ${{ matrix.tools }}
# ensure has a newline at the end
[ "$(tail -c 1 .cargo/config.toml)" != "" ] && echo >> .cargo/config.toml
cat .cargo/config.github.toml >> .cargo/config.toml
- name: Cache Rust dependencies
uses: actions/cache@v4.0.2
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git
target
key: testing-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
testing-${{ runner.os }}-${{ matrix.target }}-
testing-${{ runner.os }}-
- name: Run tests
run: cargo test --verbose --target ${{ matrix.target }}
- name: Build
if: matrix.artifact
run: cargo build --verbose --target ${{ matrix.target }}
- name: Upload Artifact
if: matrix.artifact
uses: actions/upload-artifact@v4
with:
name: spotify-quickauth-${{ github.sha }}-${{ matrix.target }}
path: target/${{ matrix.target }}/debug/spotify-quickauth

10
.hooks/link.sh vendored Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
hooks=(
"pre-commit"
"pre-push"
)
for hook in "${hooks[@]}"; do
chmod +x .hooks/$hook.sh
ln -s -f ../../.hooks/$hook.sh .git/hooks/$hook
done

10
.hooks/pre-commit.sh vendored Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
# TODO: Only run when README.md is modified
# TODO: Support partial staging, where the 'Staged' version is copied to CARGO_README.md, and then `sed` is ran.
# Remove markdown extension lines for Cargo publishing
sed '/^>\[!/d' README.md > CARGO_README.md
# Add the modified file to the commit
git add CARGO_README.md

96
.hooks/pre-push.sh vendored Executable file
View File

@@ -0,0 +1,96 @@
#!/bin/bash
# Script to be run as part of the github pre-push hook.
#
# Checks that, if there is a "version-like" tag being pushed, all the files which are supposed to contain the tag do
# actually have the correct tag value in them. If they do not, the push is blocked.
# NB: this does _not_ automatically alter the source files and commit them with the correct tag value, nor prevent the
# tag to be added to the wrong git commit locally (ie. a commit in which the source files have the wrong tag value).
# All it does is prevent the developer from pushing the 'bad tags' to remote repositories, giving him/her the chance to
# manually rectify the situation on the local repo before retrying to push.
#
# @todo could this be run as pre-commit hook instead? We have to test if adding a tag does trigger pre-commit hook...
# @see https://stackoverflow.com/questions/56538621/git-hook-to-check-tag-name
# @see https://stackoverflow.com/questions/8418071/is-there-a-way-to-check-that-a-git-tag-matches-the-content-of-the-corresponding
# for an alternative take (enforcing this with a server-side hook)
#
# NB: remember that this can be run within a windows env too, via fe. the tortoisegit or the git-4-win on the cli!
# git for windows comes with its own copy of common unix utils such as bash, grep. But they are sometimes old and/or
# buggy compared to what one gets in current linux distros :-(
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
# We do not abort the push in case there is an error in this script. No `set -e`
#set -e
# @todo detect if this is run outside git hook, and give a warning plus explain how to pass in $local_ref $local_oid $remote_ref $remote_oid
# @todo allow a git config parameter to switch on/off a 'verbose mode'
# @todo we could allow the variables `files` and `version_tag_regexp` to be set via git config parameters instead of hardcoded
# List of files which do contain the version tag
files='Cargo.toml'
# Regexp use to decide if a git tag is a version label
version_tag_regexp='^[0-9]{1,4}\.[0-9]{1,4}(\.[0-9]{1,4})?'
# Create a string of '0' chars of appropriate length for the current git version
zero="$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')"
echo "Checking commits for version tags before push..."
# check all commits which we are pushing
while read local_ref local_oid remote_ref remote_oid; do
#echo "Checking commit $local_oid ..."
# skip ref deletions
if [ "$local_oid" != "$zero" ]; then
#if [ "$remote_oid" = "$zero" ]; then
# # 'new branch'
# range="$local_oid"
#else
# # 'update to existing branch'
# range="$remote_oid..$local_oid"
#fi
# @todo in case we have a range (see commented out code 2 lines above), should we check more commits?
tags="$(git tag --points-at $local_oid)"
if [ -n "$tags" ]; then
# @todo this will not work predictably if there are 2 version tags attached to the same commit. Which probably
# there should not be anyway. Should we check for that too and abort in case?
while IFS= read -r tag; do
echo "Found tag: '$tag'..."
if [[ "$tag" =~ $version_tag_regexp ]]; then
echo "Tag looks like a version number. Checking if code is matching..."
for file in $files; do
if [ ! -f "$file" ]; then
echo "File is missing: '$file'. Please fix config of github hook script"
exit 2
fi
echo "Looking for '$tag' in '$file'"
# @todo atm if the version tag is f.e. v1.1, any file containing the string "clamav1.10' will
# match. We should improve this match to avoid such scenarios
# Note: we can not use `-i` as it crashes git-4-win's grep
if grep -F -q "$tag" "$file"; then
:
else
echo "Tag is missing from file '$file'"
exit 1
fi
done
echo "All files ok!"
break 2; # exit from both while loops: no need to check for further tags
fi
done <<< "$tags"
fi
fi
done
exit 0

View File

@@ -1,8 +1,20 @@
> [!IMPORTANT]
> This application is not 'real'; I mostly created it to learn about building a CLI application in Rust with high-end CI/CD pipelines and easy to execute multi-platform scripts.
> Interestingly enough, I never really developed the program itself, just the scripts and the CI/CD pipeline.
> And by the time I was ready to write it, the `spotify-player` project had already solved the [primary issue](https://github.com/aome510/spotify-player/issues/201#issuecomment-2439565162) I was trying to address.
> Thus, I'm archiving this project, as it's no longer necessary.
>
> This project is still pretty cool though in it's own right, with multi-platform ephemeral binaries over `curl` commands, Windows support, Linux MUSL targets (smaller binaries), ARM64 MacOS support, binary deployment to GitHub pages, automatic GitHub release uploads, and a pretty decent README - this project has it all.
# spotify-quickauth
[![Build Status](https://github.com/Xevion/spotify-quickauth/workflows/Build/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
[![Build Status](https://github.com/Xevion/spotify-quickauth/workflows/build/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
[![Testing Status](https://github.com/Xevion/spotify-quickauth/workflows/test/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
[![Crates.io](https://img.shields.io/crates/v/spotify-quickauth.svg)](https://crates.io/crates/spotify-quickauth)
![Crates.io MSRV](https://img.shields.io/crates/msrv/spotify-quickauth)
![GitHub last commit](https://img.shields.io/github/last-commit/Xevion/spotify-quickauth)
<!-- TODO: Add testing status badge -->
A simple CLI-based application for creating a `credentials.json` file, used by `librespot` derived applications, such as [spotify-player][spotify-player], [spotifyd][spotifyd], and [raspotify][raspotify].
@@ -10,8 +22,6 @@ A simple CLI-based application for creating a `credentials.json` file, used by `
- Automatically places configuration files
- No dependencies, no installation, no fuss
>This README is literally filled with lies. I'm not joking, I've just typed up a bunch of features I plan to implement, and am planning them out now. A fair amount of it works, but most of the specific options aren't currently implemented. I'm working on it, I promise!
## Quickstart
You can run this application without installing anything by using the following commands.
@@ -22,6 +32,7 @@ curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s --
The default invocation is likely fine for most users, it will try to understand the available paths for `credentials.json` to be written to, and allow you to select them.
> [!NOTE]
> Automatic detection is dependent on the related software being installed and/or relevant configuration files being present.
For **Windows**, you can paste this command into PowerShell:
@@ -34,7 +45,8 @@ iex (irm "https://xevion.github.io/spotify-quickauth/run.ps1")
This application is dead simple to use. Just run the command, and it'll tell you to connect to a fake 'device' in your Spotify interface.
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
> [!NOTE]
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
Once you connect, the credentials file will be created, and you'll be prompted to select which location(s) to place it in. Even if none of the relevant `librespot` applications are detected or installed, you can specify manual locations, or the current working directory.
@@ -42,14 +54,15 @@ Once you connect, the credentials file will be created, and you'll be prompted t
Installation is not necessary to use this application, but if you're having trouble, want to compile it yourself, or are using it frequently, you might want to install it.
>The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
> [!NOTE]
> The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
### Pre-built Binaries
Binaries are always available for download from the [releases page][releases], and they're the same ones used by the shell scripts above.
Binaries are always available for download from the [releases page][latestRelease], and they're the same ones used by the shell scripts above.
Currently, the following targets are available for download:
- x64 Linux (MUSL) `x86_64-unknown-linux-musl`
- ARM64 Linux (MUSL) `aarch64-unknown-linux-musl`
- ARMv7 Linux `armv7-unknown-linux-musleabihf`
@@ -58,14 +71,33 @@ Currently, the following targets are available for download:
- x64 Windows `x86_64-pc-windows-msvc`
- ARM64 Windows `aarch64-pc-windows-msvc`
Please [file an issue][new-issue] if you are on a platform that is not supported, or if you encounter issues with the binaries.
### Via `cargo-binstall`
> [!NOTE]
> If the package cannot be found for your target or fails to be downloaded for any reason, `cargo-binstall` will automatically fall back to building the package from source.
`cargo-binstall` is a tool that allows you to install binaries from crates.io without needing to compile them yourself.
```
cargo binstall spotify-quickauth
```
If you're curious where the binary comes from, `cargo-binstall` will likely pull the binary directly from the [latest release][latestRelease] by this repository, selecting the most appropriate target for your host.
### Manual Installation
If you'd like to use the shell script above to install the binary, you can use the `-S/--stop` flag to prevent the script from running the binary after downloading it. It implicitly applies the `--keep` flag too.
You'll need to move the binary yourself though:
```bash
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s -- -S
mv spotify-quickauth /usr/local/bin
```
Then you can move it to whatever location you'd like. Make sure that directory is in your $PATH though!
Make sure your directory of choice is in your $PATH though!
### Building from Source
@@ -82,13 +114,17 @@ spotify-quickauth --help
```
If you have any troubles building the project
- Make sure you're using a target that's supported by the project (see above).
- Certain targets may require specfic linkers. For example,
- Make sure you're using a target that's supported by the project (see above).
- Certain targets may require specfic linkers. For example,
[latestRelease]: https://github.com/Xevion/spotify-quickauth/releases/latest/
[spotify-player]: https://github.com/aome510/spotify-player
[spotifyd]: https://github.com/Spotifyd/spotifyd
[raspotify]: https://github.com/dtcooper/raspotify
[rustup]: https://rustup.rs
[git]: https://git-scm.com
[binstall]: https://github.com/cargo-bins/cargo-binstall
[quickinstall]: https://github.com/cargo-bins/cargo-quickinstall
[quickinstall]: https://github.com/cargo-bins/cargo-quickinstall
[binstall-installation]: https://github.com/cargo-bins/cargo-binstall#installation
[new-issue]: https://github.com/Xevion/spotify-quickauth/issues/new

37
CHANGELOG.md Normal file
View File

@@ -0,0 +1,37 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v0.1.7
### Added
- Archival notice.
### Changed
- Bumped MSRV to 1.79 to access stabilized `std::path::absolute`
- Adjusted `github-linguist` statistics, ignoring `.hooks`
- Widened `paths-ignore` for `build` and `test` workflows.
- Switched release step token to use `secrets.GH_RELEASE_TOKEN`, so the author is bound to `Xevion` instead of `github-actions` (a bot).
- Switched changelog format to `keep-a-changelog` style.
### Removed
- Removed `paths-ignore` for `build` workflow on `push` trigger.
## v0.1.6
### Added
- Began tracking changes in the `CHANGELOG.md` file.
- Added testing workflow to the repository, targeting only major x64 platforms.
- Added testing badge to README
### Changed
- [breaking] Lowered MSRV to 1.74 to match `librespot`'s `0.5.0-dev` branch.
- Fixed `pre-commit` hook to properly process the `CARGO_README.md` file.

2
Cargo.lock generated
View File

@@ -1287,7 +1287,7 @@ dependencies = [
[[package]]
name = "spotify-quickauth"
version = "0.1.3"
version = "0.1.7"
dependencies = [
"env_logger",
"futures",

View File

@@ -1,9 +1,9 @@
[package]
name = "spotify-quickauth"
version = "0.1.3"
version = "0.1.7"
edition = "2021"
description = "Quickly authenticate librespot-based applications with Spotify"
rust-version = "1.81"
rust-version = "1.79"
authors = ["Ryan Walters <xevion@xevion.dev>"]
homepage = "https://github.com/Xevion/spotify-quickauth"
repository = "https://github.com/Xevion/spotify-quickauth"
@@ -11,7 +11,8 @@ license = "MIT OR Apache-2.0"
readme = "CARGO_README.md"
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/spotify-quickauth-v{ version }-{ target }-{ archive-suffix }"
pkg-url = "{ repo }/releases/download/v{ version }/spotify-quickauth-v{ version }-{ target }{ archive-suffix }"
pkg-format = "tgz"
[package.metadata.binstall.signing]
algorithm = "minisign"

View File

@@ -1,12 +1,20 @@
> [!IMPORTANT]
> This application is not 'real'; I mostly created it to learn about building a CLI application in Rust with high-end CI/CD pipelines and easy to execute multi-platform scripts.
> Interestingly enough, I never really developed the program itself, just the scripts and the CI/CD pipeline.
> And by the time I was ready to write it, the `spotify-player` project had already solved the [primary issue](https://github.com/aome510/spotify-player/issues/201#issuecomment-2439565162) I was trying to address.
> Thus, I'm archiving this project, as it's no longer necessary.
>
> This project is still pretty cool though in it's own right, with multi-platform ephemeral binaries over `curl` commands, Windows support, Linux MUSL targets (smaller binaries), ARM64 MacOS support, binary deployment to GitHub pages, automatic GitHub release uploads, and a pretty decent README - this project has it all.
# spotify-quickauth
[![Build Status](https://github.com/Xevion/spotify-quickauth/workflows/Build/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
<!-- TODO: Add testing status badge -->
[![Build Status](https://github.com/Xevion/spotify-quickauth/workflows/build/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
[![Testing Status](https://github.com/Xevion/spotify-quickauth/workflows/test/badge.svg)](https://github.com/Xevion/spotify-quickauth/actions)
[![Crates.io](https://img.shields.io/crates/v/spotify-quickauth.svg)](https://crates.io/crates/spotify-quickauth)
![Crates.io MSRV](https://img.shields.io/crates/msrv/spotify-quickauth)
![GitHub last commit](https://img.shields.io/github/last-commit/Xevion/spotify-quickauth)
<!-- TODO: Add testing status badge -->
A simple CLI-based application for creating a `credentials.json` file, used by `librespot` derived applications, such as [spotify-player][spotify-player], [spotifyd][spotifyd], and [raspotify][raspotify].
@@ -14,9 +22,6 @@ A simple CLI-based application for creating a `credentials.json` file, used by `
- Automatically places configuration files
- No dependencies, no installation, no fuss
>[!WARNING]
>This README is literally filled with lies. I'm not joking, I've just typed up a bunch of features I plan to implement, and am planning them out now. A fair amount of it works, but most of the specific options aren't currently implemented. I'm working on it, I promise!
## Quickstart
You can run this application without installing anything by using the following commands.
@@ -27,7 +32,7 @@ curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s --
The default invocation is likely fine for most users, it will try to understand the available paths for `credentials.json` to be written to, and allow you to select them.
>[!NOTE]
> [!NOTE]
> Automatic detection is dependent on the related software being installed and/or relevant configuration files being present.
For **Windows**, you can paste this command into PowerShell:
@@ -40,8 +45,8 @@ iex (irm "https://xevion.github.io/spotify-quickauth/run.ps1")
This application is dead simple to use. Just run the command, and it'll tell you to connect to a fake 'device' in your Spotify interface.
>[!NOTE]
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
> [!NOTE]
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
Once you connect, the credentials file will be created, and you'll be prompted to select which location(s) to place it in. Even if none of the relevant `librespot` applications are detected or installed, you can specify manual locations, or the current working directory.
@@ -49,15 +54,15 @@ Once you connect, the credentials file will be created, and you'll be prompted t
Installation is not necessary to use this application, but if you're having trouble, want to compile it yourself, or are using it frequently, you might want to install it.
>[!NOTE]
>The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
> [!NOTE]
> The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
### Pre-built Binaries
Binaries are always available for download from the [releases page][latestRelease], and they're the same ones used by the shell scripts above.
Currently, the following targets are available for download:
- x64 Linux (MUSL) `x86_64-unknown-linux-musl`
- ARM64 Linux (MUSL) `aarch64-unknown-linux-musl`
- ARMv7 Linux `armv7-unknown-linux-musleabihf`
@@ -70,7 +75,7 @@ Please [file an issue][new-issue] if you are on a platform that is not supported
### Via `cargo-binstall`
>[!NOTE]
> [!NOTE]
> If the package cannot be found for your target or fails to be downloaded for any reason, `cargo-binstall` will automatically fall back to building the package from source.
`cargo-binstall` is a tool that allows you to install binaries from crates.io without needing to compile them yourself.
@@ -81,7 +86,6 @@ cargo binstall spotify-quickauth
If you're curious where the binary comes from, `cargo-binstall` will likely pull the binary directly from the [latest release][latestRelease] by this repository, selecting the most appropriate target for your host.
### Manual Installation
If you'd like to use the shell script above to install the binary, you can use the `-S/--stop` flag to prevent the script from running the binary after downloading it. It implicitly applies the `--keep` flag too.
@@ -110,8 +114,9 @@ spotify-quickauth --help
```
If you have any troubles building the project
- Make sure you're using a target that's supported by the project (see above).
- Certain targets may require specfic linkers. For example,
- Certain targets may require specfic linkers. For example,
[latestRelease]: https://github.com/Xevion/spotify-quickauth/releases/latest/
[spotify-player]: https://github.com/aome510/spotify-player
@@ -122,4 +127,4 @@ If you have any troubles building the project
[binstall]: https://github.com/cargo-bins/cargo-binstall
[quickinstall]: https://github.com/cargo-bins/cargo-quickinstall
[binstall-installation]: https://github.com/cargo-bins/cargo-binstall#installation
[new-issue]: https://github.com/Xevion/spotify-quickauth/issues/new
[new-issue]: https://github.com/Xevion/spotify-quickauth/issues/new

View File

@@ -1,42 +1,174 @@
use std::{env, fs::File, process::exit};
use std::{env, fs::File, path::PathBuf, process::exit};
use futures::StreamExt;
use librespot_core::config::DeviceType;
use librespot_discovery::Discovery;
use log::{info, warn};
use log::{debug, error, info, warn};
use sha1::{Digest, Sha1};
use std::io::Write;
struct Arguments {
force: bool,
path: PathBuf,
}
fn parse_arguments(args: &Vec<String>) -> Result<Arguments, String> {
let mut force: Option<bool> = None;
let mut path: Option<PathBuf> = None;
let mut skip = 1;
// If '--' is provided, all arguments before it are skipped
if let Some(index) = args.iter().position(|arg| arg == "--") {
skip = index + 1;
}
let args = &args[skip..];
debug!("Arguments: {:?}", args);
for arg in args.iter() {
match arg.as_str() {
"-f" | "--force" => {
if force.is_some() {
return Err("Force flag provided multiple times".to_string());
}
force = Some(true)
}
_ => {
if path.is_some() {
return Err("Path provided multiple times".to_string());
}
// Parse the path, validate that the argument looks like a path
let parsed = PathBuf::from(arg);
debug!("Parsed path: {}", parsed.display());
if parsed.exists() {
if parsed.is_dir() {
path = Some(parsed.join("credentials.json"));
} else if parsed.is_file() {
if path.is_some() {
return Err("Path provided multiple times".to_string());
}
path = Some(parsed);
} else {
return Err("Path is not a file or directory".to_string());
}
} else {
// File does not exist, check if it looks like a directory
if parsed.ends_with("/") || parsed.ends_with("\\") {
// If the parent directory exists, it's okay to create a directory then the file in it
if parsed.parent().is_some_and(|p| p.exists()) {
path = Some(parsed.join("credentials.json"));
} else {
return Err(
"Cannot create more than one folder for output path".to_string()
);
}
} else {
// No need to create a directory, just create the file
if parsed.parent().is_some_and(|p| p.exists()) {
path = Some(parsed);
} else {
return Err(
"Cannot create a file in a non-existent directory".to_string()
);
}
}
}
}
}
}
// If no path was provided, default to credentials.json in the current directory
let path = match path {
Some(p) => match std::path::absolute(p) {
Ok(p) => p,
Err(e) => return Err(format!("Invalid path: {}", e)),
},
None => {
// No path provided, try to identify a default in the current directory
let pwd = env::current_dir();
if pwd.is_err() {
// For some reason the current directory
return Err("Current directory is invalid or indeterminate, please provide an explicit output path".to_string());
}
// Default to credentials.json in the current directory
pwd.unwrap().join("credentials.json")
}
};
// If the *file* already exists, check if the force flag was provided
if path.exists() && path.is_file() && !force.unwrap_or(false) {
return Err(format!(
"Output file already exists, use -f to overwrite ({})",
path.display()
));
}
Ok(Arguments {
force: force.unwrap_or(false),
path,
})
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
async fn main() {
// Initialize the logger
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info")
}
env_logger::builder().init();
let credentials_file = match home::home_dir() {
// ~/.cache/spotify_player/credentials.json
Some(path) => path.join(".cache/spotify_player/credentials.json"),
None => {
warn!("Cannot determine home directory for credentials file.");
// Parse the arguments
let args = match parse_arguments(&env::args().collect()) {
Ok(a) => a,
Err(e) => {
error!("Error parsing arguments: {}", e);
exit(1);
}
};
info!("Credentials file: {}", &credentials_file.display());
// TODO: If credentials file exists, confirm overwrite
if credentials_file.exists() {
warn!("Credentials file already exists: {}", &credentials_file.display());
exit(1);
}
info!("Credentials file: {}", &args.path.display());
// TODO: If spotifyd is running, ask if shutdown is desired
let username = match env::consts::OS {
// Figure out the username
let mut username = match env::consts::OS {
"windows" => env::var("USERNAME"),
_ => env::var("USER"),
}.unwrap_or_else(|_| "unknown".to_string());
}
// Trim whitespace from the username
.map(|u| u.trim().to_string())
.unwrap_or("unknown".to_string());
// Default the username to 'unknown' if it doesn't fit the expected format
if username != "unknown" {
let valid_characters = r"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
if match &username {
u if u.is_empty() => {
warn!("Cannot determine username, defaulting to 'unknown'");
true
}
u if u.len() > 20 => {
warn!("Username is too long, defaulting to 'unknown'");
true
}
u if u.len() < 2 => {
warn!("Username is too short, defaulting to 'unknown'");
true
}
u if u.contains(|c| !valid_characters.contains(c)) => {
warn!("Username contains invalid characters, defaulting to 'unknown'");
true
}
_ => false,
} {
username = "unknown".to_string();
}
}
// Create the device metadata
let device_name = format!("spotify-quickauth-{}", username);
let device_id = hex::encode(Sha1::digest(device_name.as_bytes()));
let device_type = DeviceType::Computer;
@@ -47,35 +179,30 @@ async fn main() {
.launch()
.unwrap();
println!("Open Spotify and select output device: {}", device_name);
info!("Open Spotify and select output device: {}", device_name);
let mut written = false;
while let Some(credentials) = server.next().await {
let result = File::create("./credentials.json").and_then(|mut file| {
// Check if file exists
if args.path.exists() && !args.force {
warn!("Output file already exists (appeared after startup), use -f to overwrite");
exit(1);
}
// Write the credentials to the file
let result = File::create(&args.path).and_then(|mut file| {
let data = serde_json::to_string(&credentials)?;
write!(file, "{data}")
});
written = true;
// Check if the file was created successfully
if let Err(e) = result {
warn!("Cannot save credentials to cache: {}", e);
exit(1);
} else {
println!("Credentials saved: {}", &credentials_file.display());
info!("Credentials saved: {}", &args.path.display());
exit(0);
}
}
if !written {
warn!("No credentials were written.");
exit(1);
}
}
mod tests {
#[test]
fn test_nothing() {
assert_eq!(1, 1);
}
}
mod tests {}