Building Navidrome with Ansible
I used to be picky about not leaving custom built stuff around on my systems, as I quickly forget how they were setup, how to fix it and so forth. Stock packages from the distribution repositories, please.
Then Docker came along and changed barely anything. No snowflakey
containers unless written in a docker-compose.yml
.
But then I learned Ansible. It became my hammer, and there were a lot of nails around! And I was excited to have the cake of no custom hacks, and to eat it too, served with custom hacks!
My latest custom hack was to fix a broken FreeBSD port, Navidrome – all so I could listen to my music collection while at work. As I used Ansible this custom build is documented, automatically built and deployed so I just have to remember which playbook to run.
The role can be found on sr.ht, in my homelab repository. But I'll show the important bits below.
$ tree
.
├── defaults
│ └── main.yml
├── files
│ ├── config.toml.sample
│ └── navidrome
├── handlers
│ └── main.yml
└── tasks
└── main.yml
4 directories, 5 files
default/main.yml
All variables needed to build Navidrome on FreeBSD. I just chose a uid/gid which shouldn't override some default system account.
---
navidrome_dependencies:
- npm
- go
- git-lite
- taglib
- ffmpeg
navidrome_repo_path: "/root/navidrome"
navidrome_repo_url: "https://github.com/navidrome/navidrome.git"
navidrome_repo_version: "master"
navidrome_repo_update: true
navidrome_install: true
navidrome_force_build: false
navidrome_db_directory: "/var/db/navidrome"
navidrome_config_directory: "/usr/local/etc/navidrome"
navidrome_config: "{{ navidrome_config_directory }}/config.toml"
navidrome_user: 'navidrome'
navidrome_group: 'navidrome'
navidrome_user_id: 2014
navidrome_group_id: 2014
navidrome_autostart: true
navidrome_flags: ''
files
Two files, one is just a example configuration (config.toml.sample
)
and one is an init script. Below is files/navidrome
:
#!/bin/sh
#
# PROVIDE: navidrome
# REQUIRE: NETWORKING
# KEYWORD:
#
# Add the following lines to /etc/rc.conf to enable navidrome:
# navidrome_enable="YES"
#
# navidrome_enable (bool): Set to YES to enable navidrome
# Default: NO
# navidrome_config (str): navidrome configration file
# Default: /usr/local/etc/navidrome/config.toml
# navidrome_datafolder (str): navidrome Folder to store application data
# Default: navidrome
# navidrome_user (str): navidrome daemon user
# Default: navidrome
# navidrome_group (str): navidrome daemon group
# Default: navidrome
. /etc/rc.subr
name="navidrome"
rcvar="navidrome_enable"
load_rc_config $name
: ${navidrome_user:="navidrome"}
: ${navidrome_group:="navidrome"}
: ${navidrome_enable:="NO"}
: ${navidrome_config:="/usr/local/etc/navidrome/config.toml"}
: ${navidrome_flags=""}
: ${navidrome_facility:="daemon"}
: ${navidrome_priority:="debug"}
: ${navidrome_datafolder:="/var/db/${name}"}
required_dirs=${navidrome_datafolder}
required_files=${navidrome_config}
procname="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
start_precmd="${name}_precmd"
command=/usr/sbin/daemon
command_args="-S -l ${navidrome_facility} -s ${navidrome_priority} -T ${name} -t ${name} -p ${pidfile} \
${procname} --configfile ${navidrome_config} --datafolder ${navidrome_datafolder} ${navidrome_flags}"
navidrome_precmd()
{
install -o ${navidrome_user} /dev/null ${pidfile}
}
run_rc_command "$1"
handlers/main.yml
A simple handler to restart Navidrome:
---
- name: Restart navidrome
ansible.builtin.service:
name: navidrome
state: restarted
tasks/main.yml
Writing this down changes what we are doing from wrong to ok.
---
- name: Install required packages
ansible.builtin.package:
name: "{{ item }}"
state: present
with_items: "{{ navidrome_dependencies }}"
- name: Clone repository
ansible.builtin.git:
dest: "{{ navidrome_repo_path }}"
repo: "{{ navidrome_repo_url }}"
version: "{{ navidrome_repo_version }}"
update: "{{ navidrome_repo_update }}"
register: git_repo
- name: Run make setup
community.general.make:
chdir: "{{ navidrome_repo_path }}"
make: "/usr/local/bin/gmake"
target: "setup"
when: navidrome_force_build or git_repo.changed
- name: Run make buildall
community.general.make:
chdir: "{{ navidrome_repo_path }}"
make: "/usr/local/bin/gmake"
target: "buildall"
register: buildall
when: navidrome_force_build or git_repo.changed
- name: Install navidrome binary
ansible.builtin.copy:
src: "{{ navidrome_repo_path }}/navidrome"
dest: "/usr/local/bin/navidrome"
remote_src: true
owner: root
group: wheel
mode: '0755'
when: navidrome_install and buildall.changed
notify: Restart navidrome
- name: Install rc.d script
ansible.builtin.copy:
src: "navidrome"
dest: "/usr/local/etc/rc.d/navidrome"
owner: root
group: wheel
mode: '0755'
when: navidrome_install
- name: Create db directory
ansible.builtin.file:
path: "{{ navidrome_db_directory }}"
state: directory
owner: "{{ navidrome_user }}"
group: "{{ navidrome_group }}"
when: navidrome_install
- name: Create config directory
ansible.builtin.file:
path: "{{ navidrome_config_directory }}"
state: directory
owner: "root"
group: "wheel"
when: navidrome_install
- name: Copy sample configuration
ansible.builtin.copy:
src: "config.toml.sample"
dest: "{{ navidrome_config }}.sample"
owner: root
group: wheel
mode: '0750'
when: navidrome_install
- name: Create navidrome group
ansible.builtin.group:
name: "{{ navidrome_group }}"
gid: "{{ navidrome_group_id }}"
system: true
when: navidrome_install
- name: Create navidrome user
ansible.builtin.user:
name: "{{ navidrome_user }}"
uid: "{{ navidrome_user_id }}"
group: "{{ navidrome_group }}"
system: true
when: navidrome_install
- name: Setup navidrome in sysrc
community.general.sysrc:
name: "navidrome_{{ item.name }}"
state: present
value: "{{ item.value }}"
with_items:
- {name: 'enable', value: '{{ navidrome_autostart }}'}
- {name: 'datafolder', value: '{{ navidrome_db_directory }}'}
- {name: 'config', value: '{{ navidrome_config }}'}
- {name: 'user', value: '{{ navidrome_user }}'}
- {name: 'group', value: '{{ navidrome_group }}'}
Conclusion
I've had too much cake to eat. And I will do it again.