build: add packages for deb and rpm to release (#3202)

* support packing deb/rpm/archlinux

* .-.

* initial test

* fix postinstall, remove execstop

* bash -> sh, create toml manually if it doesn't exist (thanks debian)

* don't forget that newline

* postrm

* comments, contrib -> packaging/linux

* contrib > packaging in .goreleaser

* actually add toml

* openrc/sysv templates

* add apk. nothing else yet

* wait, we have a ntive uninstall

* fix: merge errors, move packaging to release

* chore: remove old goreleaser conf

* ci: remove `release` dependency on `docker push`

* ci: fix release version

* ci: upload packages

* ci: try to fix json file list

* ci: replace the json file list with a txt artifact

* postremove -> preremove, skip install/remove error

* actually do preremove

* better preremove

* ci: fix

* ci: fix?

* ci: clean-up

* ci: try to change labels and filenames

* ci: fix?

* ci: fix?

* ci: add `make package` target

* ci: make labels more readable

hope it doesn't break the pipeline again

* build: remove alpine and archlinux packages, for now.

---------

Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Kendall Garner 2024-10-26 17:31:45 +00:00 committed by GitHub
parent 69e2a6d620
commit 154e13f7c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 235 additions and 10 deletions

View file

@ -332,7 +332,6 @@ jobs:
sudo GIT_TAG=$GIT_TAG release/wix/build_msi.sh ${GITHUB_WORKSPACE} amd64
du -h binaries/msi/*.msi
- name: Upload MSI files
uses: actions/upload-artifact@v4
with:
@ -341,11 +340,16 @@ jobs:
retention-days: 7
release:
name: Release
needs: [build, msi, push-manifest]
name: Package/Release
needs: [build, msi]
runs-on: ubuntu-latest
outputs:
package_list: ${{ steps.set-package-list.outputs.package_list }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- uses: actions/download-artifact@v4
with:
@ -366,3 +370,56 @@ jobs:
args: "release --clean -f release/goreleaser.yml ${{ env.RELEASE_FLAGS }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Remove build artifacts
run: |
ls -l ./dist
rm ./dist/*.tar.gz ./dist/*.zip
- name: Upload all-packages artifact
uses: actions/upload-artifact@v4
with:
name: packages
path: dist/navidrome_v*
- id: set-package-list
name: Export list of generated packages
run: |
cd dist
set +x
ITEMS=$(ls navidrome_v* | sed 's/^navidrome_v[^_]*_linux_//' | jq -R -s -c 'split("\n")[:-1]')
echo $ITEMS
echo "package_list=${ITEMS}" >> $GITHUB_OUTPUT
upload-packages:
name: Upload Linux PKG
runs-on: ubuntu-latest
needs: [release]
strategy:
matrix:
item: ${{ fromJson(needs.release.outputs.package_list) }}
steps:
- name: Download all-packages artifact
uses: actions/download-artifact@v4
with:
name: packages
path: ./dist
- name: Upload all-packages artifact
uses: actions/upload-artifact@v4
with:
name: navidrome_linux_${{ matrix.item }}
path: dist/navidrome_v*_linux_${{ matrix.item }}
# delete-artifacts:
# name: Delete unused artifacts
# runs-on: ubuntu-latest
# needs: [upload-packages]
# steps:
# - name: Delete all-packages artifact
# env:
# GH_TOKEN: ${{ github.token }}
# run: |
# for artifact in $(gh api repos/${{ github.repository }}/actions/artifacts | jq -r '.artifacts[] | select(.name | startswith("packages")) | .id'); do
# gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact
# done

1
.gitignore vendored
View file

@ -11,6 +11,7 @@ wiki
TODO.md
var
navidrome.toml
!release/linux/navidrome.toml
master.zip
testDB
cache/*

View file

@ -144,6 +144,11 @@ docker-msi: ##@Cross_Compilation Build MSI installer for Windows
@du -h binaries/msi/*.msi
.PHONY: docker-msi
package: docker-build ##@Cross_Compilation Create binaries and packages for ALL supported platforms
@if [ -z `which goreleaser` ]; then echo "Please install goreleaser first: https://goreleaser.com/install/"; exit 1; fi
goreleaser release -f release/goreleaser.yml --clean --skip=publish --snapshot
.PHONY: package
get-music: ##@Development Download some free music from Navidrome's demo instance
mkdir -p music
( cd music; \

View file

@ -20,6 +20,9 @@ var (
service.StatusStopped: "Stopped",
service.StatusRunning: "Running",
}
installUser string
workingDirectory string
)
func init() {
@ -70,9 +73,11 @@ func (p *svcControl) Stop(service.Service) error {
var svcInstance = sync.OnceValue(func() service.Service {
options := make(service.KeyValue)
options["Restart"] = "on-success"
options["Restart"] = "on-failure"
options["SuccessExitStatus"] = "1 2 8 SIGKILL"
options["UserService"] = false
options["LogDirectory"] = conf.Server.DataFolder
options["SystemdScript"] = systemdScript
if conf.Server.LogFile != "" {
options["LogOutput"] = false
} else {
@ -80,12 +85,13 @@ var svcInstance = sync.OnceValue(func() service.Service {
options["LogDirectory"] = conf.Server.DataFolder
}
svcConfig := &service.Config{
UserName: installUser,
Name: "navidrome",
DisplayName: "Navidrome",
Description: "Your Personal Streaming Service",
Dependencies: []string{
"Requires=",
"After="},
"After=remote-fs.target network.target",
},
WorkingDirectory: executablePath(),
Option: options,
}
@ -108,6 +114,10 @@ func runServiceCmd(cmd *cobra.Command, _ []string) {
}
func executablePath() string {
if workingDirectory != "" {
return workingDirectory
}
ex, err := os.Executable()
if err != nil {
log.Fatal(err)
@ -141,11 +151,15 @@ func buildInstallCmd() *cobra.Command {
println("Service installed. Use 'navidrome svc start' to start it.")
}
return &cobra.Command{
cmd := &cobra.Command{
Use: "install",
Short: "Install Navidrome service.",
Run: runInstallCmd,
}
cmd.Flags().StringVarP(&installUser, "user", "u", "", "user to run service")
cmd.Flags().StringVarP(&workingDirectory, "working-directory", "w", "", "working directory of service")
return cmd
}
func buildUninstallCmd() *cobra.Command {
@ -216,3 +230,38 @@ func buildExecuteCmd() *cobra.Command {
},
}
}
const systemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path|cmdEscape}}
{{range $i, $dep := .Dependencies}}
{{$dep}} {{end}}
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
{{if .UserName}}User={{.UserName}}{{end}}
{{if .Restart}}Restart={{.Restart}}{{end}}
{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}}
TimeoutStopSec=20
RestartSec=120
EnvironmentFile=-/etc/sysconfig/{{.Name}}
DevicePolicy=closed
NoNewPrivileges=yes
PrivateTmp=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap
{{if .WorkingDirectory}}ReadWritePaths={{.WorkingDirectory|cmdEscape}}{{end}}
ProtectSystem=full
[Install]
WantedBy=multi-user.target
`

View file

@ -11,15 +11,13 @@ WantedBy=multi-user.target
User=navidrome
Group=navidrome
Type=simple
ExecStart=/usr/bin/navidrome
ExecStart=/usr/bin/navidrome --configfile "/etc/navidrome/navidrome.toml"
StateDirectory=navidrome
WorkingDirectory=/var/lib/navidrome
TimeoutStopSec=20
KillMode=process
Restart=on-failure
EnvironmentFile=-/etc/sysconfig/navidrome
# See https://www.freedesktop.org/software/systemd/man/systemd.exec.html
CapabilityBoundingSet=
DevicePolicy=closed

View file

@ -33,6 +33,58 @@ checksum:
snapshot:
version_template: "{{ .Tag }}-SNAPSHOT"
nfpms:
- id: navidrome
package_name: navidrome
homepage: https://navidrome.org
description: |-
🎧☁ Your Personal Streaming Service
maintainer: Deluan Quintão <deluan at navidrome.org>
license: GPL-3.0
formats:
- deb
- rpm
dependencies:
- ffmpeg
suggests:
- mpv
overrides:
rpm:
dependencies:
- "(ffmpeg or ffmpeg-free)"
contents:
- src: release/linux/navidrome.toml
dst: /etc/navidrome/navidrome.toml
type: "config|noreplace"
file_info:
mode: 0644
owner: navidrome
group: navidrome
- dst: /var/lib/navidrome
type: dir
file_info:
owner: navidrome
group: navidrome
- dst: /opt/navidrome/music
type: dir
file_info:
owner: navidrome
group: navidrome
scripts:
preinstall: "release/linux/preinstall.sh"
postinstall: "release/linux/postinstall.sh"
preremove: "release/linux/preremove.sh"
release:
draft: true
mode: append

View file

@ -0,0 +1,2 @@
DataFolder = "/var/lib/navidrome"
MusicFolder = "/opt/navidrome/music"

View file

@ -0,0 +1,25 @@
#!/bin/sh
# It is possible for a user to delete the configuration file in such a way that
# the package manager (in particular, deb) thinks that the file exists, while it is
# no longer on disk. Specifically, doing a `rm /etc/navidrome/navidrome.toml`
# without something like `apt purge navidrome` will result in the system believing that
# the file still exists. In this case, during isntall it will NOT extract the configuration
# file (as to not override it). Since `navidrome service install` depends on this file existing,
# we will create it with the defaults anyway.
if [ ! -f /etc/navidrome/navidrome.toml ]; then
printf "No navidrome.toml detected, creating in postinstall\n"
printf "DataFolder = \"/var/lib/navidrome\"\n" > /etc/navidrome/navidrome.toml
printf "MusicFolder = \"/opt/navidrome/music\"\n" >> /etc/navidrome/navidrome.toml
fi
postinstall_flag="/var/lib/navidrome/.installed"
if [ ! -f "$postinstall_flag" ]; then
# The primary reason why this would fail is if the service was already installed AND
# someone manually removed the .installed flag. In this case, ignore the error
navidrome service install --user navidrome --working-directory /var/lib/navidrome --configfile /etc/navidrome/navidrome.toml || :
touch "$postinstall_flag"
fi

6
release/linux/preinstall.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/sh
if ! getent passwd navidrome > /dev/null 2>&1; then
printf "Creating default Navidrome user\n"
useradd --home-dir /var/lib/navidrome --create-home --system --user-group navidrome
fi

View file

@ -0,0 +1,30 @@
#!/bin/sh
action=$1
remove() {
postinstall_flag="/var/lib/navidrome/.installed"
if [ -f "$postinstall_flag" ]; then
# If this fails, ignore it
navidrome service uninstall || :
rm "$postinstall_flag"
printf "The following may still be present (especially if you have not done a purge):\n"
printf "1. /etc/navidrome/navidrome.toml (configuration file)\n"
printf "2. /var/lib/navidrome (database/cache)\n"
printf "3. /opt/navidrome (default location for music)\n"
printf "4. The Navidrome user (user name navidrome)\n"
fi
}
case "$action" in
"1" | "upgrade")
# For an upgrade, do nothing
# Leave the service file untouched
# This is relevant for RPM/DEB-based installs
;;
*)
remove
;;
esac