Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot]
f0bbfa85aa
Merge d48b0a2f65 into 8ab9463c9a 2026-10-23 04:09:38 +00:00
Kingkor Roy Tirtho
8ab9463c9a feat(packaging): Flathub, AUR, WinGet and Chocolatey publishing
- Flatpak manifest (GNOME 51, libvlc built from source) installing from a new
  portable spotube-linux-<version>-<arch>.tar.xz, shared with the AUR package
- Release workflow builds and attaches the Linux tarballs for x86_64 and aarch64
- Package files are JMustache templates rendered by :composeApp:renderPackaging<Target>
  with the version, release asset checksums and git-cliff release notes; the
  metainfo release history is generated
- Publish workflow (workflow_dispatch, release tag input) renders once and pushes to
  Flathub, the AUR, WinGet (PR from team-spotube/winget-pkgs) and Chocolatey
- Chocolatey and WinGet updated for the NSIS installer
2026-10-23 10:08:29 +06:00
dependabot[bot]
d48b0a2f65
chore(deps): bump softprops/action-gh-release from 2 to 3
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 23:42:25 +00:00
24 changed files with 1199 additions and 132 deletions

View File

@ -0,0 +1,274 @@
name: Publish
# Publishes an existing GitHub release (made by spotube-release-binary.yml) to the package managers.
# The `render` job checks out the release tag, downloads its assets and renders packaging/<target> with
# Gradle (:composeApp:renderPackaging<Target>: version, checksums, git-cliff release notes). The publish
# jobs only push what it rendered.
#
# Secrets:
# FLATHUB_TOKEN PAT with push access to team-spotube/org.spotube.spotube
# AUR_USERNAME, AUR_EMAIL, AUR_SSH_PRIVATE_KEY AUR account that maintains spotube-bin
# WINGET_TOKEN classic PAT (public_repo) that can push to team-spotube/winget-pkgs and open PRs
# CHOCO_API_KEY chocolatey.org API key
on:
workflow_dispatch:
inputs:
tag:
description: Release tag to publish (vX.Y.Z)
required: true
type: string
jobs:
description: Jobs to run (flathub,aur,winget,chocolatey)
required: true
type: string
default: flathub,aur,winget,chocolatey
aur_pkgrel:
description: AUR pkgrel (bump to re-release the same version)
required: true
type: string
default: '1'
dry_run:
description: Dry run (render the packages, push nothing)
required: true
type: boolean
default: true
permissions:
contents: read
env:
TAG: ${{ inputs.tag }}
GH_TOKEN: ${{ github.token }}
jobs:
render:
name: Render packages
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Validate tag and find release
id: version
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::tag must look like vX.Y.Z, got '$TAG'"
exit 1
fi
is_draft=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq .isDraft)
# The packages download from the public release URLs, which a draft doesn't have yet
if [ "$is_draft" = "true" ] && [ "$DRY_RUN" != "true" ]; then
echo "::error::release $TAG is still a draft; publish it first"
exit 1
fi
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
# Full history: git-cliff builds the release notes from it
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 21
# Configuring :composeApp runs cargo (Gobley)
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
- name: Install git-cliff
uses: taiki-e/install-action@v2
with:
tool: git-cliff
- name: Download release assets
run: |
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir release-assets \
--pattern 'spotube-linux-*.tar.xz' --pattern 'Spotube-windows-x86_64-setup.exe'
- name: Render packages
env:
PKGREL: ${{ inputs.aur_pkgrel }}
run: |
chmod +x gradlew
./gradlew :composeApp:renderPackagingFlatpak :composeApp:renderPackagingAur \
:composeApp:renderPackagingWinget :composeApp:renderPackagingChocolatey \
-Pnightly=false -Ppkgrel="$PKGREL" -PpackagingArtifactsDir=release-assets
for f in composeApp/build/packaging/{flatpak/*.yml,aur/PKGBUILD,winget/*.yaml,chocolatey/*.nuspec}; do
echo "::group::$f"
cat "$f"
echo "::endgroup::"
done
- name: Upload rendered packages
uses: actions/upload-artifact@v7
with:
name: packaging
path: |
composeApp/build/packaging/flatpak
composeApp/build/packaging/aur
composeApp/build/packaging/winget
composeApp/build/packaging/chocolatey
# Chocolatey embeds the installer in the package
- name: Upload Windows installer
if: contains(inputs.jobs, 'chocolatey')
uses: actions/upload-artifact@v7
with:
name: windows-installer
path: release-assets/Spotube-windows-x86_64-setup.exe
flathub:
name: Flathub
needs: render
if: contains(inputs.jobs, 'flathub')
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.render.outputs.version }}
FLATHUB_REPO: team-spotube/org.spotube.spotube
steps:
- uses: actions/download-artifact@v8
with:
name: packaging
path: packaging
- uses: actions/checkout@v7
with:
repository: ${{ env.FLATHUB_REPO }}
token: ${{ secrets.FLATHUB_TOKEN }}
path: flathub
- name: Commit
working-directory: flathub
run: |
cp ../packaging/flatpak/org.spotube.spotube.yml .
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "update-$VERSION"
git add org.spotube.spotube.yml
git diff --cached
git commit -m "Update to $VERSION"
- name: Push and open PR
if: ${{ !inputs.dry_run }}
working-directory: flathub
env:
GH_TOKEN: ${{ secrets.FLATHUB_TOKEN }}
run: |
git push -u origin "update-$VERSION"
gh pr create --repo "$FLATHUB_REPO" --head "update-$VERSION" \
--title "Update to $VERSION" \
--body "Release notes: https://github.com/$GITHUB_REPOSITORY/releases/tag/$TAG"
aur:
name: AUR
needs: render
if: contains(inputs.jobs, 'aur')
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
name: packaging
path: packaging
- name: Publish to AUR
if: ${{ !inputs.dry_run }}
uses: KSXGitHub/github-actions-deploy-aur@v4.2.0
with:
pkgname: spotube-bin
pkgbuild: packaging/aur/PKGBUILD
commit_username: ${{ secrets.AUR_USERNAME }}
commit_email: ${{ secrets.AUR_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Update to ${{ needs.render.outputs.version }}-${{ inputs.aur_pkgrel }}
winget:
name: WinGet
needs: render
if: contains(inputs.jobs, 'winget')
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.render.outputs.version }}
FORK: team-spotube/winget-pkgs
MANIFEST_DIR: manifests/k/KRTirtho/Spotube
steps:
- uses: actions/download-artifact@v8
with:
name: packaging
path: packaging
- name: Sync fork with upstream
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ secrets.WINGET_TOKEN }}
run: gh repo sync "$FORK" --source microsoft/winget-pkgs --branch master
# winget-pkgs is huge: only fetch this package's directory
- uses: actions/checkout@v7
if: ${{ !inputs.dry_run }}
with:
repository: ${{ env.FORK }}
ref: master
token: ${{ secrets.WINGET_TOKEN }}
path: winget-pkgs
sparse-checkout: ${{ env.MANIFEST_DIR }}
- name: Push and open PR
if: ${{ !inputs.dry_run }}
working-directory: winget-pkgs
env:
GH_TOKEN: ${{ secrets.WINGET_TOKEN }}
run: |
branch="KRTirtho.Spotube-$VERSION"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$branch"
mkdir -p "$MANIFEST_DIR/$VERSION"
cp ../packaging/winget/*.yaml "$MANIFEST_DIR/$VERSION/"
git add "$MANIFEST_DIR/$VERSION"
git commit -m "New version: KRTirtho.Spotube version $VERSION"
git push -u origin "$branch"
gh pr create --repo microsoft/winget-pkgs --base master --head "${FORK%%/*}:$branch" \
--title "New version: KRTirtho.Spotube version $VERSION" \
--body "Release notes: https://github.com/$GITHUB_REPOSITORY/releases/tag/$TAG"
chocolatey:
name: Chocolatey
needs: render
if: contains(inputs.jobs, 'chocolatey')
runs-on: windows-latest
env:
VERSION: ${{ needs.render.outputs.version }}
steps:
- uses: actions/download-artifact@v8
with:
name: packaging
path: packaging
- uses: actions/download-artifact@v8
with:
name: windows-installer
path: packaging/chocolatey/tools
- name: Pack
run: choco pack packaging/chocolatey/spotube.nuspec --outputdirectory dist
- name: Upload package
uses: actions/upload-artifact@v7
with:
name: chocolatey-nupkg
path: dist/*.nupkg
- name: Publish to Chocolatey
if: ${{ !inputs.dry_run }}
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
run: choco push "dist/spotube.$env:VERSION.nupkg" --source https://push.chocolatey.org/ --api-key "$env:CHOCO_API_KEY"

View File

@ -155,7 +155,10 @@ jobs:
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
# Full history: the metainfo's release notes come from git-cliff
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Download maven local
uses: actions/download-artifact@v8
@ -196,6 +199,30 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :composeApp:packageGraalvmDeb :composeApp:packageGraalvmRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
# Portable tarball the AUR and Flathub packages install from. Like deb/rpm it uses the system's VLC,
# GTK and WebKitGTK, so it is packed from the distributable before the AppImage build replaces it
- name: Install git-cliff
uses: taiki-e/install-action@v2
with:
tool: git-cliff
- name: Package Linux tarball
run: |
if [ "${{ inputs.release_type }}" = "nightly" ]; then
VERSION=nightly
else
VERSION=$(./gradlew -q :composeApp:printVersion)
fi
# Desktop entry + metainfo, with the release history from git-cliff
./gradlew :composeApp:renderPackagingLinux -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
packaging/package-linux-tarball.sh "$VERSION" "${{ inputs.release_type }}" .
- name: Upload tarball
uses: actions/upload-artifact@v7
with:
name: linux-tarball
path: spotube-linux-*-x86_64.tar.xz
# Only the AppImage bundles VLC, GTK and WebKitGTK; deb/rpm depend on the distro packages
- name: Package Linux AppImage
env:
@ -241,7 +268,10 @@ jobs:
timeout-minutes: 120
runs-on: ubuntu-24.04-arm
steps:
# Full history: the metainfo's release notes come from git-cliff
- uses: actions/checkout@v7
with:
fetch-depth: 0
# The arm64 runner image ships without an Android SDK/NDK, but :composeApp configuration needs both
# (Gobley's cargo plugin resolves the NDK directory for the Android Rust targets)
@ -289,6 +319,30 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :composeApp:packageGraalvmDeb :composeApp:packageGraalvmRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
# Portable tarball the AUR and Flathub packages install from. Like deb/rpm it uses the system's VLC,
# GTK and WebKitGTK, so it is packed from the distributable before the AppImage build replaces it
- name: Install git-cliff
uses: taiki-e/install-action@v2
with:
tool: git-cliff
- name: Package Linux tarball
run: |
if [ "${{ inputs.release_type }}" = "nightly" ]; then
VERSION=nightly
else
VERSION=$(./gradlew -q :composeApp:printVersion)
fi
# Desktop entry + metainfo, with the release history from git-cliff
./gradlew :composeApp:renderPackagingLinux -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }}
packaging/package-linux-tarball.sh "$VERSION" "${{ inputs.release_type }}" .
- name: Upload tarball
uses: actions/upload-artifact@v7
with:
name: linux-tarball-arm64
path: spotube-linux-*-aarch64.tar.xz
# Only the AppImage bundles VLC, GTK and WebKitGTK; deb/rpm depend on the distro packages
- name: Package Linux AppImage
env:
@ -507,7 +561,7 @@ jobs:
run: |
echo "## Downloads" > release-notes.md
echo "" >> release-notes.md
for dir in android-apk linux-deb linux-rpm linux-appimage linux-deb-arm64 linux-rpm-arm64 linux-appimage-arm64 windows-exe macos-dmg; do
for dir in android-apk linux-deb linux-rpm linux-appimage linux-tarball linux-deb-arm64 linux-rpm-arm64 linux-appimage-arm64 linux-tarball-arm64 windows-exe macos-dmg; do
if [ -d "$dir" ]; then
echo "### $dir" >> release-notes.md
for f in "$dir"/*; do
@ -540,8 +594,10 @@ jobs:
linux-deb/*.deb
linux-rpm/*.rpm
linux-appimage/*.AppImage
linux-tarball/*.tar.xz
linux-deb-arm64/*.deb
linux-rpm-arm64/*.rpm
linux-appimage-arm64/*.AppImage
linux-tarball-arm64/*.tar.xz
windows-exe/*.exe
macos-dmg/*.dmg

View File

@ -66,6 +66,12 @@
- **Android signing**: decodes `secrets.KEYSTORE` (base64) → `composeApp/upload-keystore.jks`, writes signing config into `local.properties` from secrets.
- `create-release` job (depends on all builds) uses `softprops/action-gh-release@v2` to create the GitHub release with all artifacts attached.
## Package managers (`packaging/`, `.github/workflows/spotube-publish-binary.yml`)
- `packaging/<target>` (`linux`, `aur`, `flatpak`, `winget`, `chocolatey`) holds JMustache templates (`*.mustache`) plus files copied as is. `:composeApp:renderPackaging<Target>` renders them into `composeApp/build/packaging/<target>` (`RenderPackagingTask` in buildSrc documents the template data). Never hand-write versions, checksums or release notes into these files.
- Checksums come from release assets in `-PpackagingArtifactsDir=<dir>`; a template using a missing value fails the render. Release notes come from `git-cliff --context` with `cliff.toml` (`GitCliffContextTask`): needs `git-cliff` on the PATH and full git history. `gradle.properties` defaults to `nightly=true`, which leaves commits unreleased: pass `-Pnightly=false` for a stable render.
- `packaging/package-linux-tarball.sh` packs `spotube-linux-<version>-<arch>.tar.xz` (the AUR and Flathub source) from the GraalVM distributable and the rendered `linux` dir; the release workflow builds it before the AppImage step.
- The publish workflow (`workflow_dispatch`, input `tag`) renders once, then pushes to Flathub (`team-spotube/org.spotube.spotube`), the AUR (`spotube-bin`), WinGet (PR from `team-spotube/winget-pkgs`) and Chocolatey.
## OpenCode Agent MCP Execution Directives
### 1. Code Discovery & Context Search

View File

@ -1,5 +1,6 @@
// Build logic for the Linux AppImage bundle (see DownloadLinuxGtkBundleTask). Lives in buildSrc so the IDE
// resolves it; an `apply(from = ...)` script plugin gets no Kotlin classpath in Android Studio.
// Build logic: the Linux AppImage bundle (DownloadLinuxGtkBundleTask) and the package manager manifests
// (GitCliffContextTask, RenderPackagingTask). Lives in buildSrc so the IDE resolves it; an
// `apply(from = ...)` script plugin gets no Kotlin classpath in Android Studio.
plugins {
`kotlin-dsl`
}
@ -7,3 +8,7 @@ plugins {
repositories {
mavenCentral()
}
dependencies {
implementation(libs.jmustache)
}

View File

@ -0,0 +1,8 @@
// Shares the root version catalog (gradle/libs.versions.toml) with the build logic
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Writes git-cliff's changelog context (every release with its parsed, grouped commits) as JSON, using
// the repository's cliff.toml. RenderPackagingTask turns it into release notes for the package manifests.
// Needs `git-cliff` on the PATH and the full git history (CI: `fetch-depth: 0`).
package org.spotube.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.work.DisableCachingByDefault
import javax.inject.Inject
@DisableCachingByDefault(because = "Reads the git history, which is not a declared input")
abstract class GitCliffContextTask @Inject constructor(private val execOperations: ExecOperations) : DefaultTask() {
@get:Input
abstract val executable: Property<String>
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val configFile: RegularFileProperty
@get:Internal
abstract val repositoryDir: DirectoryProperty
/** Tag for the commits since the last release (`--tag`); unset leaves them unreleased (nightlies). */
@get:Input
@get:Optional
abstract val tag: Property<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
init {
group = "distribution"
description = "Writes the git-cliff changelog context (releases and their commits) as JSON."
executable.convention("git-cliff")
// New commits and tags change the result without touching any declared input
outputs.upToDateWhen { false }
}
@TaskAction
fun run() {
val output = outputFile.get().asFile
output.parentFile.mkdirs()
val args = buildList {
add(executable.get())
addAll(listOf("--config", configFile.get().asFile.absolutePath, "--context"))
tag.orNull?.let { addAll(listOf("--tag", it)) }
addAll(listOf("--output", output.absolutePath))
}
try {
execOperations.exec {
workingDir = repositoryDir.get().asFile
commandLine(args)
}
} catch (e: Exception) {
throw GradleException(
"Running git-cliff failed (${args.joinToString(" ")}). Install it from https://git-cliff.org " +
"and make sure the full git history is available.",
e,
)
}
}
}

View File

@ -0,0 +1,264 @@
/*
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Renders one package manager's directory (packaging/<target>) into the build dir: `*.mustache` files are
// rendered with JMustache (suffix dropped), everything else is copied as is.
//
// Template data:
// version, tag, pkgrel, repositoryUrl
// artifacts.<key>.{fileName, url, sha256, sha256Upper} release assets found in artifactsDir
// release.{version, date, url, groups, notes} the release being packaged
// releases tagged releases, newest first (metainfo)
// groups[].{title, entries[].text} changelog grouped like cliff.toml
// #indent2 / #indent4 indent a multi-line block (YAML block scalars)
//
// Missing variables fail the render, so a manifest never ships with an empty checksum or version.
// HTML/XML escaping is on for .xml and .nuspec output only.
package org.spotube.gradle
import com.samskivert.mustache.Mustache
import com.samskivert.mustache.MustacheException
import groovy.json.JsonSlurper
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.security.MessageDigest
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
@CacheableTask
abstract class RenderPackagingTask : DefaultTask() {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val templateDir: DirectoryProperty
@get:Input
abstract val version: Property<String>
@get:Input
abstract val pkgrel: Property<String>
@get:Input
abstract val repositoryUrl: Property<String>
/** Release assets by template key, e.g. `linuxX86_64` -> `spotube-linux-6.0.0-x86_64.tar.xz`. */
@get:Input
abstract val artifactNames: MapProperty<String, String>
/** Directory holding the downloaded release assets; only needed by templates that use `artifacts`. */
@get:Internal
abstract val artifactsDir: DirectoryProperty
/** The release assets that exist in [artifactsDir]; their checksums are part of the output. */
@get:Input
protected val artifactChecksums: Map<String, String>
get() = artifactFiles().mapValues { (_, file) -> sha256(file) }
/** git-cliff `--context` output (GitCliffContextTask). */
@get:InputFile
@get:Optional
@get:PathSensitive(PathSensitivity.NONE)
abstract val changelogContext: RegularFileProperty
/** How many tagged releases `releases` lists. */
@get:Input
abstract val maxReleases: Property<Int>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
init {
group = "distribution"
pkgrel.convention("1")
maxReleases.convention(10)
}
@TaskAction
fun render() {
val source = templateDir.get().asFile
val target = outputDir.get().asFile
target.deleteRecursively()
target.mkdirs()
val data = templateData()
source.walkTopDown().filter { it.isFile }.forEach { file ->
val relative = file.relativeTo(source).path
if (!relative.endsWith(TEMPLATE_SUFFIX)) {
file.copyTo(target.resolve(relative), overwrite = true)
return@forEach
}
val output = target.resolve(relative.removeSuffix(TEMPLATE_SUFFIX))
val compiler = Mustache.compiler()
.escapeHTML(output.extension in XML_EXTENSIONS)
.emptyStringIsFalse(true)
.zeroIsFalse(true)
val rendered = try {
compiler.compile(file.readText()).execute(data)
} catch (e: MustacheException) {
throw GradleException(
"Rendering $relative failed: ${e.message?.trimEnd('.')}. Templates that use `artifacts` need " +
"-PpackagingArtifactsDir=<dir with the release assets>.",
e,
)
}
output.parentFile.mkdirs()
output.writeText(rendered)
}
}
private fun templateData(): Map<String, Any> {
val version = version.get()
val tag = "v$version"
val repositoryUrl = repositoryUrl.get()
val downloadUrl = "$repositoryUrl/releases/download/$tag"
val artifacts = artifactFiles().mapValues { (_, file) ->
val sha256 = sha256(file)
mapOf(
"fileName" to file.name,
"url" to "$downloadUrl/${file.name}",
"sha256" to sha256,
"sha256Upper" to sha256.uppercase(),
)
}
val changelog = changelogContext.orNull?.asFile?.let(::readChangelog).orEmpty()
val tagged = changelog.filter { it.version != null }.sortedByDescending { it.timestamp }
// The release being packaged; nightlies have no tag, so they get the unreleased commits
val current = changelog.firstOrNull { it.version == tag }
?: changelog.firstOrNull { it.version == null }
?: ChangelogRelease(tag, Instant.now().epochSecond, emptyList())
return mapOf(
"version" to version,
"tag" to tag,
"pkgrel" to pkgrel.get(),
"repositoryUrl" to repositoryUrl,
"artifacts" to artifacts,
"release" to releaseData(current, version, repositoryUrl),
"releases" to tagged.take(maxReleases.get()).map {
releaseData(it, it.version!!.removePrefix("v"), repositoryUrl)
},
"indent2" to indent(2),
"indent4" to indent(4),
)
}
private fun artifactFiles(): Map<String, File> {
val dir = artifactsDir.orNull?.asFile ?: return emptyMap()
return artifactNames.get()
.mapValues { (_, name) -> dir.resolve(name) }
.filterValues { it.isFile }
}
private fun releaseData(release: ChangelogRelease, version: String, repositoryUrl: String): Map<String, Any> {
val groups = release.commits
.groupBy { it.group }
.toSortedMap()
.map { (group, commits) ->
mapOf(
// cliff.toml orders groups with an HTML comment prefix: "<!-- 0 -->Features"
"title" to group.replace(HTML_COMMENT, "").trim(),
"entries" to commits.map { mapOf("text" to it.text) },
)
}
val notes = groups.joinToString("\n\n") { group ->
@Suppress("UNCHECKED_CAST")
val entries = group["entries"] as List<Map<String, String>>
"${group["title"]}\n" + entries.joinToString("\n") { "- ${it["text"]}" }
}
return mapOf(
"version" to version,
"date" to LocalDate.ofInstant(Instant.ofEpochSecond(release.timestamp), ZoneOffset.UTC).toString(),
"url" to "$repositoryUrl/releases/tag/v$version",
"groups" to groups,
"notes" to notes,
)
}
@Suppress("UNCHECKED_CAST")
private fun readChangelog(file: File): List<ChangelogRelease> {
val releases = JsonSlurper().parse(file) as List<Map<String, Any?>>
return releases.map { release ->
val commits = (release["commits"] as List<Map<String, Any?>>).mapNotNull { commit ->
val group = commit["group"] as String? ?: return@mapNotNull null
val message = (commit["message"] as String).plainText().replaceFirstChar { it.uppercase() }
val scope = commit["scope"] as String?
val breaking = commit["breaking"] == true
val text = buildString {
if (breaking) append("Breaking: ")
if (scope != null) append("$scope: ")
append(message)
}
ChangelogCommit(group, text)
}
ChangelogRelease(
version = release["version"] as String?,
timestamp = (release["timestamp"] as Number?)?.toLong() ?: Instant.now().epochSecond,
commits = commits,
)
}
}
private data class ChangelogRelease(val version: String?, val timestamp: Long, val commits: List<ChangelogCommit>)
private data class ChangelogCommit(val group: String, val text: String)
private companion object {
const val TEMPLATE_SUFFIX = ".mustache"
val XML_EXTENSIONS = setOf("xml", "nuspec")
val HTML_COMMENT = Regex("<!--.*?-->")
val MARKDOWN_LINK = Regex("""\[([^\]]+)]\([^)]+\)""")
// cliff.toml's commit preprocessors turn issue numbers into Markdown links
fun String.plainText() = replace(MARKDOWN_LINK, "$1").replace("**", "").trim()
fun indent(spaces: Int) = Mustache.Lambda { fragment, out ->
val prefix = " ".repeat(spaces)
out.write(fragment.execute().lines().joinToString("\n") { if (it.isEmpty()) it else prefix + it })
}
fun sha256(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { input ->
val buffer = ByteArray(1 shl 16)
while (true) {
val read = input.read(buffer)
if (read < 0) break
digest.update(buffer, 0, read)
}
}
return digest.digest().joinToString("") { "%02x".format(it) }
}
}
}

View File

@ -25,6 +25,8 @@ import org.jetbrains.compose.reload.gradle.ComposeHotRun
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.gradle.api.tasks.Copy
import org.spotube.gradle.DownloadLinuxGtkBundleTask
import org.spotube.gradle.GitCliffContextTask
import org.spotube.gradle.RenderPackagingTask
import java.io.FileInputStream
import java.util.Properties
@ -76,6 +78,37 @@ tasks.register("printVersion") {
val appVersion = appVersionName
doLast { println(appVersion) }
}
// Package manager manifests: packaging/<target> rendered into build/packaging/<target> with the version, the
// release assets' checksums (-PpackagingArtifactsDir=<dir>) and the git-cliff changelog. See RenderPackagingTask.
val gitCliffContext = tasks.register<GitCliffContextTask>("gitCliffContext") {
configFile.set(rootProject.layout.projectDirectory.file("cliff.toml"))
repositoryDir.set(rootProject.layout.projectDirectory)
// Nightlies are not a release: their commits stay unreleased
if (!isNightly) tag.set("v$appVersionName")
outputFile.set(layout.buildDirectory.file("packaging/changelog-context.json"))
}
val packagingRootDir = rootProject.layout.projectDirectory
val packagingArtifactsDir = packagingRootDir.dir(providers.gradleProperty("packagingArtifactsDir"))
listOf("linux", "aur", "flatpak", "winget", "chocolatey").forEach { target ->
tasks.register<RenderPackagingTask>("renderPackaging${target.replaceFirstChar { it.uppercase() }}") {
description = "Renders packaging/$target into build/packaging/$target."
templateDir.set(packagingRootDir.dir("packaging/$target"))
version.set(appVersionName)
pkgrel.set(providers.gradleProperty("pkgrel").orElse("1"))
repositoryUrl.set(appRepositoryUrl)
artifactNames.set(
mapOf(
"linuxX86_64" to "spotube-linux-$appVersionName-x86_64.tar.xz",
"linuxAarch64" to "spotube-linux-$appVersionName-aarch64.tar.xz",
"windowsX64" to "Spotube-windows-x86_64-setup.exe",
)
)
artifactsDir.set(packagingArtifactsDir)
changelogContext.set(gitCliffContext.flatMap { it.outputFile })
outputDir.set(layout.buildDirectory.dir("packaging/$target"))
}
}
val githubRepositoryPath = repositoryUrl.removePrefix("https://github.com/")
val latestReleaseApiUrl = "https://api.github.com/repos/$githubRepositoryPath/releases/latest"
val generatedAppInfoDir = layout.buildDirectory.dir("generated/appBuildInfo/commonMain/kotlin")

View File

@ -77,8 +77,11 @@ vanniktechMavenPublish = "0.37.0"
uniffi = "0.3.7"
kotlinx-atomicfu = "0.33.0"
mokkery = "3.3.0"
jmustache = "1.16"
[libraries]
# buildSrc: renders the package manager manifests in packaging/
jmustache = { module = "com.samskivert:jmustache", version.ref = "jmustache" }
appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" }
cache4k = { module = "io.github.reactivecircus.cache4k:cache4k", version.ref = "cache4k" }
androidx-car-app = { module = "androidx.car.app:app", version.ref = "carApp" }

View File

@ -1,19 +0,0 @@
pkgbase = spotube-bin
pkgdesc = Open source Music client that doesn't require Premium nor uses Electron! Available for both desktop & mobile!
pkgver = 4.0.0
pkgrel = 1
url = https://spotube.org
arch = x86_64
license = AGPL-3.0-or-later
depends = mpv
depends = libappindicator-gtk3
depends = libsecret
depends = jsoncpp
depends = libnotify
depends = xdg-user-dirs
depends = webkit2gtk-4.1
optdepends = yt-dlp-git
source = https://github.com/team-spotube/spotube/releases/download/v3.7.1/spotube-linux-3.7.1-x86_64.tar.xz
md5sums = 475b1ae9b08f27743a4d4749391ae3db
pkgname = spotube-bin

View File

@ -1,40 +0,0 @@
# Maintainer: Kingkor Roy Tirtho <krtirho@gmail.com>
pkgname=spotube-bin
pkgver=%{{SPOTUBE_VERSION}}%
pkgrel=%{{PKGREL}}%
epoch=
pkgdesc="Open source Music client that doesn't require Premium nor uses Electron! Available for both desktop & mobile!"
arch=(x86_64)
url="https://spotube.org"
license=('AGPL-3.0-or-later')
groups=()
depends=('mpv' 'libappindicator-gtk3' 'libsecret' 'jsoncpp' 'libnotify' 'xdg-user-dirs' 'webkit2gtk-4.1')
makedepends=()
checkdepends=()
optdepends=('yt-dlp-git')
provides=()
conflicts=()
replaces=()
backup=()
options=()
install=
changelog=
source=("https://github.com/team-spotube/spotube/releases/download/v${pkgver}/spotube-linux-${pkgver}-x86_64.tar.xz")
noextract=()
md5sums=(%{{LINUX_MD5}}%)
validpgpkeys=()
package() {
install -dm755 "${pkgdir}/usr/share/icons/spotube"
install -dm755 "${pkgdir}/usr/share/applications"
install -dm755 "${pkgdir}/usr/share/appdata"
install -dm755 "${pkgdir}/usr/share/${pkgname}"
install -dm755 "${pkgdir}/usr/bin"
mv ./spotube.desktop "${pkgdir}/usr/share/applications"
mv ./spotube-logo.png "${pkgdir}/usr/share/icons/spotube/"
mv ./com.github.KRTirtho.Spotube.appdata.xml "${pkgdir}/usr/share/appdata/spotube.appdata.xml"
cp -ra ./data ./lib ./spotube "${pkgdir}/usr/share/${pkgname}"
sed -i 's|com.github.KRTirtho.Spotube|spotube|' "${pkgdir}/usr/share/appdata/spotube.appdata.xml"
ln -s "/usr/share/${pkgname}/spotube" "${pkgdir}/usr/bin/spotube"
}

View File

@ -0,0 +1,32 @@
# Maintainer: Kingkor Roy Tirtho <krtirho@gmail.com>
pkgname=spotube-bin
pkgver={{version}}
pkgrel={{pkgrel}}
pkgdesc="Open source, plugin powered music streaming client"
arch=(x86_64 aarch64)
url="https://spotube.org"
license=('AGPL-3.0-or-later')
# libvlc + plugins for playback (FFmpeg demux/decode, GnuTLS for HTTPS streams, PulseAudio/PipeWire output),
# WebKitGTK 4.1 for the webview, and the X11/GL/ALSA libraries the AWT/Skiko natives link
depends=(
'libvlc' 'vlc-plugins-base' 'vlc-plugin-ffmpeg' 'vlc-plugin-gnutls' 'vlc-plugin-pulse'
'webkit2gtk-4.1' 'gtk3' 'glib2' 'dbus' 'alsa-lib' 'libglvnd' 'fontconfig' 'freetype2'
'libx11' 'libxext' 'libxi' 'libxrender' 'libxtst' 'zlib'
)
provides=('spotube')
conflicts=('spotube')
options=('!strip' '!debug')
source_x86_64=("https://github.com/team-spotube/spotube/releases/download/v${pkgver}/spotube-linux-${pkgver}-x86_64.tar.xz")
source_aarch64=("https://github.com/team-spotube/spotube/releases/download/v${pkgver}/spotube-linux-${pkgver}-aarch64.tar.xz")
sha256sums_x86_64=('{{artifacts.linuxX86_64.sha256}}')
sha256sums_aarch64=('{{artifacts.linuxAarch64.sha256}}')
package() {
cd "spotube-linux-${pkgver}-${CARCH}"
install -dm755 "${pkgdir}/opt" "${pkgdir}/usr/bin" "${pkgdir}/usr/share"
cp -a app "${pkgdir}/opt/spotube"
cp -a share/. "${pkgdir}/usr/share/"
ln -s /opt/spotube/spotube "${pkgdir}/usr/bin/spotube"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter
enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<!-- == PACKAGE SPECIFIC SECTION == -->
<id>spotube</id>
<version>%{{SPOTUBE_VERSION}}%</version>
<packageSourceUrl>https://github.com/team-spotube/spotube/tree/master/choco-struct</packageSourceUrl>
<owners>Kingkor Roy Tirtho</owners>
<!-- ============================== -->
<!-- == SOFTWARE SPECIFIC SECTION == -->
<title>spotube (Install)</title>
<authors>Kingkor Roy Tirtho</authors>
<projectUrl>https://spotube.org</projectUrl>
<iconUrl>
https://rawcdn.githack.com/team-spotube/spotube/7edb0bb834eb18c05551e30a891720a6abf53dbe/assets/branding/spotube-logo.png</iconUrl>
<copyright>2022 Spotube</copyright>
<!-- If there is a license Url available, it is required for the community feed -->
<licenseUrl>https://github.com/team-spotube/spotube/blob/master/LICENSE</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/team-spotube/spotube</projectSourceUrl>
<docsUrl>https://spotube.org</docsUrl>
<bugTrackerUrl>https://github.com/team-spotube/spotube/issues/new</bugTrackerUrl>
<tags>spotube music audio youtube flutter</tags>
<summary>🎧 Open source music client that doesn't require Premium nor uses Electron! Available
for both desktop &amp; mobile! </summary>
<description>
Spotube is a Flutter based lightweight music client. It utilizes the power
of music metadata providers &amp; Youtube's public API &amp; creates a hazardless, performant
&amp; resource
friendly User Experience
# Features
- Open source/libre software
- Anonymous/guest login
- Cross platform support
- No telemetry, diagnostics or user data collection
- Lightweight &amp; resource-friendly
- Native performance (Thanks to Flutter+Skia)
- Playback control is done locally instead of on the server
- Small size &amp; less data usage
- No ads since it uses all public &amp; free APIs (It is still recommended
to support the creators by watching/liking/subscribing to the artists' YouTube channels or
liking their tracks on different music platforms.)
- Time synced lyrics
- Downloadable tracks
</description>
<releaseNotes>https://github.com/team-spotube/spotube/releases/tag/v%{{SPOTUBE_VERSION}}%</releaseNotes>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter
enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<!-- == PACKAGE SPECIFIC SECTION == -->
<id>spotube</id>
<version>{{version}}</version>
<packageSourceUrl>https://github.com/team-spotube/spotube/tree/master/packaging/chocolatey</packageSourceUrl>
<owners>Kingkor Roy Tirtho</owners>
<!-- ============================== -->
<!-- == SOFTWARE SPECIFIC SECTION == -->
<title>spotube (Install)</title>
<authors>Team Spotube</authors>
<projectUrl>https://spotube.org</projectUrl>
<iconUrl>
https://rawcdn.githack.com/team-spotube/spotube/69a310c78f5ceaf4eab7dfee98f187d38211c9ba/assets/branding/spotube-logo.png</iconUrl>
<copyright>2026 Kingkor Roy Tirtho and Spotube Contributors</copyright>
<!-- If there is a license Url available, it is required for the community feed -->
<licenseUrl>https://github.com/team-spotube/spotube/blob/master/LICENSE</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/team-spotube/spotube</projectSourceUrl>
<docsUrl>https://spotube.org</docsUrl>
<bugTrackerUrl>https://github.com/team-spotube/spotube/issues/new</bugTrackerUrl>
<tags>spotube music audio music-player streaming lyrics plugins foss</tags>
<summary>Open source, plugin powered music streaming client</summary>
<description>
A cross-platform, extensible, open source music streaming platform. Bring your own music
metadata, playlists and audio sources with plugins made by the community or by yourself.
# Features
- Plugin powered: supports any platform or custom music service through plugins
- Community driven plugins for popular platforms, or create your own
- Freely downloadable tracks with tagged metadata
- Time synced lyrics regardless of plugin support
- No telemetry, diagnostics or user data collection
- Small size, low data usage and native performance
- Open source/libre software
</description>
<releaseNotes>{{release.url}}
{{release.notes}}</releaseNotes>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>

View File

@ -7,17 +7,17 @@ in verifying that this package's contents are trustworthy.
Please go to releases page
https://github.com/team-spotube/spotube/releases
Download same version as this choco package (example for v%{{SPOTUBE_VERSION}}%)
https://github.com/team-spotube/spotube/releases/tag/v%{{SPOTUBE_VERSION}}%
Download same version as this choco package (v{{version}})
https://github.com/team-spotube/spotube/releases/tag/v{{version}}
1. get hashes. Run:
powershell -command Get-FileHash tools\Spotube-windows-x86_64-setup.exe
2. The checksums should match the following:
---
Version Hashes for v%{{SPOTUBE_VERSION}}%
Version Hashes for v{{version}}
Algorithm Hash Path
--------- ---- ----
SHA256 %{{WIN_SHA256}}% tools\Spotube-windows-x86_64-setup.exe
SHA256 {{artifacts.windowsX64.sha256Upper}} tools\Spotube-windows-x86_64-setup.exe

View File

@ -5,13 +5,15 @@ $fileLocation = Join-Path $toolsDir 'Spotube-windows-x86_64-setup.exe'
$packageArgs = @{
packageName = $env:ChocolateyPackageName
unzipLocation = $toolsDir
fileType = 'exe' #only one of these: exe, msi, msu
file = $fileLocation
fileType = 'exe'
file = $fileLocation
softwareName = 'spotube*' #part or all of the Display Name as you see it in Programs and Features. It should be enough to be unique
silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' # Inno Setup
softwareName = 'Spotube*'
silentArgs = '/S' # NSIS (electron-builder one-click, per-user)
validExitCodes= @(0)
}
Install-ChocolateyInstallPackage @packageArgs
Install-ChocolateyInstallPackage @packageArgs
# The installer was only needed to install; don't keep a copy in the package directory
Remove-Item $fileLocation -Force -ErrorAction SilentlyContinue

View File

@ -1,17 +1,23 @@
$ErrorActionPreference = 'Stop'; # stop on all errors
$packageArgs = @{
packageName = $env:ChocolateyPackageName
softwareName = 'spotube*'
softwareName = 'Spotube*'
fileType = 'exe'
validExitCodes= @(0, 3010, 1605, 1614, 1641)
silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' # Inno Setup
validExitCodes= @(0)
}
[array]$key = Get-UninstallRegistryKey -SoftwareName $packageArgs['softwareName']
if ($key.Count -eq 1) {
$key | % {
$packageArgs['file'] = "$($_.UninstallString)" #NOTE: You may need to split this if it contains spaces, see below
# electron-builder writes UninstallString as "<dir>\Uninstall Spotube.exe" /currentuser
if ($_.UninstallString -match '^\s*"([^"]+)"\s*(.*)$') {
$packageArgs['file'] = $Matches[1]
$packageArgs['silentArgs'] = "$($Matches[2]) /S".Trim()
} else {
$packageArgs['file'] = $_.UninstallString
$packageArgs['silentArgs'] = '/S'
}
Uninstall-ChocolateyPackage @packageArgs
}
@ -22,4 +28,4 @@ if ($key.Count -eq 1) {
Write-Warning "To prevent accidental data loss, no programs will be uninstalled."
Write-Warning "Please alert package maintainer the following keys were matched:"
$key | % {Write-Warning "- $($_.DisplayName)"}
}
}

View File

@ -0,0 +1,112 @@
id: org.spotube.spotube
# GTK 3 and WebKitGTK 4.1 (the webview) come from the GNOME runtime
runtime: org.gnome.Platform
runtime-version: '51'
sdk: org.gnome.Sdk
command: spotube
finish-args:
- --share=ipc
- --share=network
# Compose renders through AWT/Skiko, which needs X11 (XWayland on Wayland sessions)
- --socket=x11
- --socket=pulseaudio
- --device=dri
# Downloaded tracks go to ~/Downloads/Spotube; local tracks are scanned from ~/Music and ~/Downloads
- --filesystem=xdg-download
- --filesystem=xdg-music:ro
# Tray icon (StatusNotifierItem)
- --talk-name=org.kde.StatusNotifierWatcher
cleanup:
- /include
- /lib/pkgconfig
- /share/man
- /share/doc
- '*.la'
- '*.a'
modules:
# libvlc for vlcj (the audio player). Audio only: no video outputs, UI or Lua; decoding and demuxing
# go through the runtime's FFmpeg (proprietary codecs come from the auto-installed codecs-extra extension).
- name: vlc
build-options:
env:
# configure looks for a c99/c11 wrapper for its build-time tools
BUILDCC: gcc -std=gnu11
config-opts:
- --disable-vlc
- --disable-qt
- --disable-skins2
- --disable-ncurses
- --disable-lua
- --disable-a52
- --disable-dbus
- --disable-xcb
- --disable-xvideo
- --disable-wayland
- --disable-vdpau
- --disable-libva
- --disable-gles2
- --disable-sdl-image
- --disable-chromecast
- --disable-udev
- --disable-v4l2
- --disable-dc1394
- --disable-dv1394
- --disable-linsys
- --disable-dvbpsi
- --disable-bluray
- --disable-dvdread
- --disable-dvdnav
- --disable-vcd
- --disable-libcddb
- --disable-screen
- --disable-addonmanagermodules
- --disable-update-check
cleanup:
- /share/applications
- /share/icons
- /share/kde4
- /share/metainfo
- /share/solid
- /share/vlc
sources:
- type: archive
url: https://download.videolan.org/pub/videolan/vlc/3.0.24/vlc-3.0.24.tar.xz
sha256: e7cab503d1d7d5849b89d2cf0e1ee60d0ef6d012407791b644b9cfc0cc225fdf
x-checker-data:
type: anitya
project-id: 6504
stable-only: true
versions: {<: '4.0'}
url-template: https://download.videolan.org/pub/videolan/vlc/$version/vlc-$version.tar.xz
# The GraalVM native image, repackaged from the release tarball (packaging/package-linux-tarball.sh)
- name: spotube
buildsystem: simple
build-commands:
- cp -a app ${FLATPAK_DEST}/lib/spotube
- install -d ${FLATPAK_DEST}/bin
- ln -s ../lib/spotube/spotube ${FLATPAK_DEST}/bin/spotube
- cp -a share/. ${FLATPAK_DEST}/share/
sources:
- type: archive
only-arches: [x86_64]
url: {{artifacts.linuxX86_64.url}}
sha256: {{artifacts.linuxX86_64.sha256}}
x-checker-data:
type: json
url: https://api.github.com/repos/team-spotube/spotube/releases/latest
version-query: .tag_name | sub("^v"; "")
url-query: '"https://github.com/team-spotube/spotube/releases/download/v" + $version + "/spotube-linux-" + $version + "-x86_64.tar.xz"'
is-main-source: true
- type: archive
only-arches: [aarch64]
url: {{artifacts.linuxAarch64.url}}
sha256: {{artifacts.linuxAarch64.sha256}}
x-checker-data:
type: json
url: https://api.github.com/repos/team-spotube/spotube/releases/latest
version-query: .tag_name | sub("^v"; "")
url-query: '"https://github.com/team-spotube/spotube/releases/download/v" + $version + "/spotube-linux-" + $version + "-aarch64.tar.xz"'

View File

@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Name=Spotube
GenericName=Music Player
Comment=Open source, plugin powered music streaming client
Exec=spotube %U
Icon=org.spotube.spotube
Terminal=false
Categories=AudioVideo;Audio;Music;Player;
Keywords=music;streaming;player;audio;lyrics;playlist;
StartupNotify=true
StartupWMClass=org-spotube-spotube-MainKt

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.spotube.spotube</id>
<!-- The Flutter-era app shipped on Flathub as com.github.KRTirtho.Spotube -->
<provides>
<id>com.github.KRTirtho.Spotube</id>
</provides>
<replaces>
<id>com.github.KRTirtho.Spotube</id>
</replaces>
<name>Spotube</name>
<summary>Plugin powered, open source music streaming client</summary>
<developer id="org.spotube">
<name>Team Spotube</name>
</developer>
<metadata_license>CC0-1.0</metadata_license>
<project_license>AGPL-3.0-or-later</project_license>
<description>
<p>
A cross-platform, extensible, open source music streaming platform. Bring your own music
metadata, playlists and audio sources with plugins made by the community or by yourself.
</p>
<ul>
<li>Plugin powered: supports any platform or custom music service through plugins</li>
<li>Community driven plugins for popular platforms, or create your own</li>
<li>Freely downloadable tracks with tagged metadata</li>
<li>Time synced lyrics regardless of plugin support</li>
<li>No telemetry, diagnostics or user data collection</li>
<li>Small size, low data usage and native performance</li>
</ul>
</description>
<launchable type="desktop-id">org.spotube.spotube.desktop</launchable>
<categories>
<category>AudioVideo</category>
<category>Audio</category>
<category>Music</category>
</categories>
<keywords>
<keyword>music</keyword>
<keyword>streaming</keyword>
<keyword>player</keyword>
<keyword>lyrics</keyword>
</keywords>
<url type="homepage">https://spotube.org</url>
<url type="bugtracker">https://github.com/team-spotube/spotube/issues</url>
<url type="vcs-browser">https://github.com/team-spotube/spotube</url>
<url type="donation">https://opencollective.com/spotube</url>
<url type="contribute">https://github.com/team-spotube/spotube/blob/master/CONTRIBUTION.md</url>
<screenshots>
<screenshot type="default">
<caption>Spotube on the desktop</caption>
<image>https://raw.githubusercontent.com/team-spotube/spotube/master/assets/branding/spotube-screenshot.png</image>
</screenshot>
</screenshots>
<branding>
<color type="primary" scheme_preference="light">#84b2ff</color>
<color type="primary" scheme_preference="dark">#01185c</color>
</branding>
<content_rating type="oars-1.1" />
<requires>
<display_length compare="ge">360</display_length>
</requires>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<!-- Generated from the git-cliff changelog (RenderPackagingTask) -->
<releases>
{{#releases}}
<release version="{{version}}" date="{{date}}">
<url type="details">{{url}}</url>
{{#notes}}
<description>
{{#groups}}
<p>{{title}}</p>
<ul>
{{#entries}}
<li>{{text}}</li>
{{/entries}}
</ul>
{{/groups}}
</description>
{{/notes}}
</release>
{{/releases}}
</releases>
</component>

View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Packs the GraalVM native distributable into spotube-linux-<version>-<arch>.tar.xz, the
# portable tarball the AUR (spotube-bin) and Flathub (org.spotube.spotube) packages install from.
#
# Run after `:composeApp:createGraalvmNativeDistributable` WITHOUT -PappImage=true (the tarball uses the
# system's or the Flatpak runtime's VLC, GTK 3 and WebKitGTK, like the deb/rpm) and
# `:composeApp:renderPackagingLinux` (desktop entry + metainfo with the release notes).
#
# Usage: packaging/package-linux-tarball.sh <version> [stable|nightly] [out-dir]
#
# Layout:
# spotube-linux-<version>-<arch>/
# app/ binary + the JDK/Skiko libraries it loads
# share/applications/org.spotube.spotube.desktop
# share/metainfo/org.spotube.spotube.metainfo.xml
# share/icons/hicolor/512x512/apps/org.spotube.spotube.png
# LICENSE
set -euo pipefail
version="${1:?usage: $0 <version> [stable|nightly] [out-dir]}"
channel="${2:-stable}"
out_dir="${3:-.}"
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
app_dir="${SPOTUBE_APP_DIR:-$root/composeApp/build/compose/binaries/main/graalvm-app/org.spotube.spotube}"
linux_dir="$root/composeApp/build/packaging/linux"
case "$(uname -m)" in
x86_64 | amd64) arch=x86_64 ;;
aarch64 | arm64) arch=aarch64 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
if [[ ! -x "$app_dir/spotube" ]]; then
echo "No native distributable at $app_dir; run :composeApp:createGraalvmNativeDistributable first" >&2
exit 1
fi
if [[ ! -f "$linux_dir/org.spotube.spotube.metainfo.xml" ]]; then
echo "No rendered metainfo in $linux_dir; run :composeApp:renderPackagingLinux first" >&2
exit 1
fi
# An AppImage build (-PappImage=true) leaves VLC plugins and a GTK/WebKit tree in the same dir
if [[ -d "$app_dir/plugins" || -d "$app_dir/usr" ]]; then
echo "$app_dir bundles VLC/GTK (an -PappImage=true build); rebuild without it" >&2
exit 1
fi
name="spotube-linux-$version-$arch"
staging="$(mktemp -d)"
trap 'rm -rf "$staging"' EXIT
dest="$staging/$name"
mkdir -p "$dest/share/applications" "$dest/share/metainfo" "$dest/share/icons/hicolor/512x512/apps"
cp -a "$app_dir" "$dest/app"
rm -f "$dest/app/.complete"
install -m644 "$linux_dir/org.spotube.spotube.desktop" "$dest/share/applications/"
install -m644 "$linux_dir/org.spotube.spotube.metainfo.xml" "$dest/share/metainfo/"
install -m644 "$root/composeApp/icons/$channel/icon.png" "$dest/share/icons/hicolor/512x512/apps/org.spotube.spotube.png"
install -m644 "$root/LICENSE" "$dest/LICENSE"
mkdir -p "$out_dir"
tarball="$(cd "$out_dir" && pwd)/$name.tar.xz"
tar -C "$staging" --sort=name --owner=0 --group=0 --numeric-owner \
--mtime="@${SOURCE_DATE_EPOCH:-$(git -C "$root" log -1 --format=%ct)}" \
-cf - "$name" | xz -T0 -9 > "$tarball"
echo "$tarball"

View File

@ -0,0 +1,21 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json
PackageIdentifier: KRTirtho.Spotube
PackageVersion: {{version}}
Platform:
- Windows.Desktop
MinimumOSVersion: 10.0.0.0
# electron-builder one-click NSIS installer (Nucleus), installs per user; winget's nullsoft default /S is silent
InstallerType: nullsoft
Scope: user
InstallModes:
- silent
- silentWithProgress
UpgradeBehavior: install
ReleaseDate: {{release.date}}
Installers:
- Architecture: x64
InstallerUrl: {{artifacts.windowsX64.url}}
InstallerSha256: {{artifacts.windowsX64.sha256Upper}}
ManifestType: installer
ManifestVersion: 1.10.0

View File

@ -0,0 +1,34 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json
PackageIdentifier: KRTirtho.Spotube
PackageVersion: {{version}}
PackageLocale: en-US
Publisher: Team Spotube
PublisherUrl: https://github.com/team-spotube
PublisherSupportUrl: https://github.com/team-spotube/spotube/issues
PrivacyUrl: https://spotube.org/p/privacy
Author: Team Spotube
PackageName: Spotube
PackageUrl: https://spotube.org
License: AGPL-3.0-or-later
LicenseUrl: https://github.com/team-spotube/spotube/blob/master/LICENSE
Copyright: Copyright (c) 2022-2026 Kingkor Roy Tirtho and Spotube Contributors
ShortDescription: Open source, plugin powered music streaming client
Description: |-
A cross-platform, extensible, open source music streaming platform. Bring your own music
metadata, playlists and audio sources with plugins made by the community or by yourself.
Moniker: spotube
Tags:
- audio
- music
- music-player
- streaming
- lyrics
- plugins
{{#release.notes}}
ReleaseNotes: |-
{{#indent2}}{{release.notes}}{{/indent2}}
{{/release.notes}}
ReleaseNotesUrl: {{release.url}}
ManifestType: defaultLocale
ManifestVersion: 1.10.0

View File

@ -0,0 +1,7 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json
PackageIdentifier: KRTirtho.Spotube
PackageVersion: {{version}}
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.10.0