Minimal installation of Kodi on Ubuntu Server 24.10 with N100

The initial network topology is shown in the image above. While this is a tutorial, let’s briefly recap the background.

The TV Viewing Dilemma

I recently purchased a low-power mini-PC and decided to shut down my Synology NAS. This raised the question of how to watch TV in the living room. Many online recommendations suggested a second-hand Magic Box for around ¥50. I purchased an S905L3A model with Wi-Fi and pre-installed popular TV apps. Combined with an infrared remote, it seemed like a user-friendly solution for older users.

However, I soon encountered issues. The remote controls often malfunctioned, and the device frequently restarted or returned to the home screen during operation or video playback. The seller suggested a power supply issue, as I was using an old Huawei router’s power adapter. I tried swapping it with a higher-powered one from the fiber optic modem (0.5A), which improved the situation but didn’t completely resolve the problem. After some negotiation, the seller agreed to a replacement.

The most frustrating issue was the inability to install Jellyfin TV, despite successfully installing BBLL. Online discussions revealed that these operator-modified ROMs often lack essential system modules. A patch was available, but it required manual compilation and had no guarantee of stability. I decided to abandon this approach.

Another option was to flash the device’s firmware, which was one of the reasons I chose this particular model in the first place. However, I came across a comment from another buyer mentioning the difficulty and cost of reverting to the original firmware without a backup image. I decided against this option as well.

After receiving the replacement, I only briefly tested it for the restart issue and lost interest in further tinkering. I decided to use it in the bedroom for my child to watch cartoons.

Exploring the Mini-PC as a Media Center

So, we’re back to square one: what to use as a media player for the living room TV? I eventually decided to repurpose the mini-PC. Initially, I had set it up as a headless Ubuntu server, but given the N100’s relatively new CPU, I upgraded to the latest Ubuntu 24.10 (Oracular Oriole) for better driver compatibility. It’s also upgradeable to the next LTS version, Plucky Puffin, when it’s released.

Among Linux-based media players, Kodi is undoubtedly the king. It boasts a mature ecosystem, extensive documentation, and various installation methods. The simplest approach is to use a pre-configured distribution like LibreELEC, but considering my plans to install Proxmox VE and the lack of OpenWrt needs, I opted for a direct installation on the host system. I initially considered using Docker, but the available options either had outdated versions or lacked a significant user base, increasing the risk of encountering issues.

Fortunately, I found two guides on installing Kodi minimally on Ubuntu server:

  • Kodi Setup Guide 1
  • Kodi Setup Guide 2

However, they rely on the older X11 GUI framework, which requires numerous dependencies and potential compatibility issues on newer kernels. Another similar approach is the one-click installation script: Boot2Kodi. For a deeper understanding of X11, Wayland, and GBM, refer to this explanation: Kodi Standalone Service.

Further digging led me to a helpful forum post: Kodi Forum Post. It suggests prioritizing hardware driver compatibility (refer to Jellyfin Hardware Acceleration and Intel Media Driver) and then following the official Kodi installation instructions.

Specific Implementation Steps

Hardware and Driver Configuration

Hardware and Driver Information::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜  ~ uname -a
Linux n100 6.11.0-9-generic #9-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct 14 13:19:59 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
➜ ~ sudo lspci -v | grep i915
Kernel driver in use: i915
Kernel modules: i915, xe
➜ ~ sudo lshw | grep i915
configuration: depth=32 driver=i915 latency=0 mode=1920x1080 resolution=1920,1080 visual=truecolor xres=1920 yres=1080
➜ ~ sudo dmesg | grep i915
[ 13.326033] i915 0000:00:02.0: [drm] Found ALDERLAKE_P/ADL-N (device ID 46d1) display version 13.00 stepping D0
[ 13.326927] i915 0000:00:02.0: [drm] VT-d active for gfx access
[ 13.370261] i915 0000:00:02.0: vgaarb: deactivate vga console
[ 13.371357] i915 0000:00:02.0: [drm] Using Transparent Hugepages
[ 13.371930] i915 0000:00:02.0: vgaarb: VGA decodes changed: olddecodes=io+mem,decodes=io+mem:owns=io+mem
[ 13.374690] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/adlp_dmc.bin (v2.20)
[ 13.587503] i915 0000:00:02.0: [drm] GT0: GuC firmware i915/tgl_guc_70.bin version 70.29.2
[ 13.587515] i915 0000:00:02.0: [drm] GT0: HuC firmware i915/tgl_huc.bin version 7.9.3
[ 13.592056] i915 0000:00:02.0: [drm] GT0: HuC: authenticated for all workloads
[ 13.592757] i915 0000:00:02.0: [drm] GT0: GUC: submission enabled
[ 13.592762] i915 0000:00:02.0: [drm] GT0: GUC: SLPC enabled
[ 13.593204] i915 0000:00:02.0: [drm] GT0: GUC: RC enabled
[ 13.594830] mei_pxp 0000:00:16.0-fbf6fcf1-96cf-4e2e-a6a6-1bab8cbe36b1: bound 0000:00:02.0 (ops i915_pxp_tee_component_ops [i915])
[ 13.595027] i915 0000:00:02.0: [drm] Protected Xe Path (PXP) protected content support initialized
[ 13.595034] mei_hdcp 0000:00:16.0-b638ab7e-94e2-4ea2-a552-d1c54b627f04: bound 0000:00:02.0 (ops i915_hdcp_ops [i915])
[ 13.630189] [drm] Initialized i915 1.6.0 for 0000:00:02.0 on minor 1
[ 13.706559] fbcon: i915drmfb (fb0) is primary device
[ 13.784629] i915 0000:00:02.0: [drm] fb0: i915drmfb frame buffer device
[ 13.796441] sof-audio-pci-intel-tgl 0000:00:1f.3: bound 0000:00:02.0 (ops i915_audio_component_bind_ops [i915])

Based on the output of the commands, it seems that the necessary drivers for the Intel GPU are already installed and functioning correctly.

Compile and install kodi

Unfortunately, as of May 2023, the official Kodi PPA is no longer maintained. This means we’ll need to compile Kodi from source. Besides, Compile with specific params enabled HDR pass through based on reddit post.

Install libdisplay before configure build:

1
2
3
4
5
6
7
8
9
10
11
12
13
cd ~ && wget https://gitlab.freedesktop.org/emersion/libdisplay-info/-/archive/0.2.0/libdisplay-info-0.2.0.tar.gz
tar xzf libdisplay-info-0.2.0.tar.gz && cd libdisplay-info-0.2.0
mkdir build && cd build
meson setup --prefix=/usr --buildtype=release
ninja
sudo ninja install
cd ~/kodi-build
cmake ../kodi -DCMAKE_INSTALL_PREFIX=/usr/local -DCORE_PLATFORM_NAME=gbm -DAPP_RENDER_SYSTEM=gles -DENABLE_VAAPI=ON
cmake --build . -- VERBOSE=1 -j$(getconf _NPROCESSORS_ONLN)
cd ~/kodi
sudo make -j$(getconf _NPROCESSORS_ONLN) -C tools/depends/target/binary-addons PREFIX=/usr/local ADDONS="audioencoder.flac audioencoder.lame audioencoder.vorbis audioencoder.wav"
sudo make install
cd ../kodi-build && sudo make install

If you met error during configure(as below), use kodi source code from git master instead of release file, it maybe encountered pcre2 package:

CMake Error at /usr/share/cmake-3.30/Modules/FindPackageHandleStandardArgs.cmake:233 (message):
Could NOT find PCRE (missing: PCRE_LIBRARY PCRE_INCLUDE_DIR)
Call Stack (most recent call first):
/usr/share/cmake-3.30/Modules/FindPackageHandleStandardArgs.cmake:603 (_FPHSA_FAILURE_MESSAGE)
cmake/modules/FindPCRE.cmake:126 (find_package_handle_standard_args)
cmake/scripts/common/Macros.cmake:403 (find_package)
cmake/scripts/common/Macros.cmake:417 (find_package_with_ver)
CMakeLists.txt:261 (core_require_dep)

-- Configuring incomplete, errors occurred!

Kodi-gbm takes 30 minutes to compile, then we can test with ‘sudo./kodi-gbm’. When playing MKV video, the CPU occupation is between 10% and 45%. Note that if you don’t give sufficient permissions to the current user (especially for the render and input groups), be sure to root, or you may not be able to use the keyboard or mouse. After successful execution, a.kodi folder is generated in the user root directory to hold the program’s data.

Troubleshooting

Remote Control

The choice is Yatse, setup according to documents. After successfully logging in, you can unplug the mouse or keyboard. The Pro version adds screen projection, jellyfin/emby/plex clients, offline playback and other features for only $3.49. After testing, Yatse actually called vlc player or MX player to play, while itself just act as poster wall. DLNA costs extra for transcoding, and cloud transcoding isn’t very practical either. So it’s a matter of opinion.

Another pitfall is that the first item in the power menu is to shut down the host directly, rather than exit the program. When kodi is not running, the Power menu can only be started via Remote Wake Up (WoL) (so set kodi to boot). What I wanted is: the power button remotely run ‘systemctl start’, or quitting the app instead of shutting down host while kodi is running.

synchronize kodi on/off to TV

The original idea was to listen on the WoL port and start kodi when the host receives magic packet, but it is still not very elegant. The next idea was to poll the TV’s power status to turn kodi on and off.

The key is the value of the ‘/sys/class/drm/card0-HDMI-A-2/status’ file. card0 stands for graphics card, which may become card1 when restarted. HDMI is the interface type, depending on the actual situation. ‘A-2’ represents the interface number, for example ‘A-1’ is type-c interface. Use a jsonapi http post to exit safely. To start, the systemctl command is used directly. Based on the answer given by the AI, we first create a bash file and give it permission to execute:

/usr/local/bin/hdmi-monitor.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/bin/bash

KODI_HOST="user:passwd@localhost"
KODI_PORT="8080"
LOG_FILE="/var/log/hdmi-monitor.log"
MAX_RETRIES=2
RETRY_DELAY=2

log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" >> "$LOG_FILE"
}

check_kodi_available() {
local timeout=1
curl -s --connect-timeout $timeout "http://$KODI_HOST:$KODI_PORT/jsonrpc" >/dev/null
return $?
}

send_kodi_command() {
local method="$1"
local retry_count=0
local success=false

while [ $retry_count -lt $MAX_RETRIES ] && [ "$success" = false ]; do
if ! check_kodi_available; then
log_message "Kodi HTTP server not responding, attempt $(($retry_count + 1))/$MAX_RETRIES"
sleep $RETRY_DELAY
retry_count=$((retry_count + 1))
continue
fi

response=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"$method\",\"id\":1}" \
"http://$KODI_HOST:$KODI_PORT/jsonrpc")

if [ $? -eq 0 ] && [ ! -z "$response" ]; then
if echo "$response" | grep -q "error"; then
log_message "Error executing Kodi command $method: $response"
else
log_message "Successfully executed Kodi command: $method"
success=true
break
fi
else
log_message "Failed to execute Kodi command $method, attempt $(($retry_count + 1))/$MAX_RETRIES"
fi

retry_count=$((retry_count + 1))
[ $retry_count -lt $MAX_RETRIES ] && sleep $RETRY_DELAY
done

if [ "$success" = false ]; then
log_message "Failed to execute Kodi command $method after $MAX_RETRIES attempts"
return 1
fi
return 0
}

stop_kodi_gracefully() {
log_message "Attempting to stop Kodi gracefully"

# Quit the application
if ! send_kodi_command "Application.Quit"; then
log_message "Error: Failed to quit Kodi gracefully, forcing service stop"
else
sleep 2
fi

# Finally stop the service
if systemctl is-active --quiet kodi-gbm.service; then
log_message "Error: Failed to stop kodi-gbm.service"
return 1
fi
log_message "Successfully stopped Kodi and its service"
return 0
}

# Ensure log file exists and is writable
touch "$LOG_FILE" 2>/dev/null || {
echo "Error: Cannot create or access log file at $LOG_FILE"
exit 1
}

# Try to read status from card0 first, then card1 if that fails
STATUS=$(cat "/sys/class/drm/card0-HDMI-A-2/status" 2>/dev/null)
CARD="card0"

if [ $? -ne 0 ]; then
STATUS=$(cat "/sys/class/drm/card1-HDMI-A-2/status" 2>/dev/null)
CARD="card1"
if [ $? -ne 0 ]; then
log_message "Error: Could not read HDMI status from either card0 or card1"
exit 1
fi
fi

if [ "$STATUS" = "connected" ]; then
if ! systemctl is-active --quiet kodi-gbm.service; then
if ! systemctl start kodi-gbm.service; then
log_message "Error: Failed to start kodi-gbm.service"
exit 1
fi
fi
else
if systemctl is-active --quiet kodi-gbm.service; then
if ! stop_kodi_gracefully; then
log_message "Error: Failed to stop Kodi gracefully"
exit 1
fi
fi
fi

exit 0

And the files related to system service:

/etc/systemd/system/hdmi-monitor.service
[Unit]
Description=HDMI Connection Monitor Service
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hdmi-monitor.sh

[Install]
WantedBy=multi-user.target
/etc/systemd/system/hdmi-monitor.timer
[Unit]
Description=Timer for HDMI Connection Monitor Service

[Timer]
OnBootSec=30
OnUnitActiveSec=5s
Unit=hdmi-monitor.service

[Install]
WantedBy=timers.target
/etc/logrotate.d/hdmi-monitor
/var/log/hdmi-monitor.log {
weekly
rotate 4
compress
missingok
notifempty
}

Finally start the service:

sudo systemctl daemon-reload
sudo systemctl enable hdmi-monitor.timer
sudo systemctl start hdmi-monitor.timer
### check service
sudo systemctl status hdmi-monitor.timer

Audio not working

If audio user group is granted, and still can’t play the sound, then it may be that the audio service component is missing. For instance, only one item in the audio output device list: ‘sof-hda-dsp, Analog’. First, install the alsa package, and then check if the headphones and HDMI can emit sound:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
➜  ~ asudo apt install alsa-base
### Check volume
➜ ~ asudo alsamixer
### Check Hardware Devices
➜ ~ asudo aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: sofhdadsp [sof-hda-dsp], device 0: HDA Analog (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: sofhdadsp [sof-hda-dsp], device 3: HDMI1 (*) []
Subdevices: 0/1
Subdevice #0: subdevice #0
card 0: sofhdadsp [sof-hda-dsp], device 4: HDMI2 (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: sofhdadsp [sof-hda-dsp], device 5: HDMI3 (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: sofhdadsp [sof-hda-dsp], device 31: HDA Analog Deep Buffer (*) []
Subdevices: 1/1
Subdevice #0: subdevice #0
### test speaker though headphone
sudo aplay /usr/share/sounds/alsa/*
### test speaker though HDMI
sudo speaker-test --channels 2 --test wav --device hw:0,3

If pass the test, refer to this article to install pipewire(use pulseaudio instead before ubuntu 22.04). Note that Pipewire-pulse is only for compatibility consideration, so:

sudo add-apt-repository ppa:pipewire-debian/pipewire-upstream
sudo apt install pipewire pipewire-audio-client-libraries gstreamer1.0-pipewire libspa-0.2-bluetooth libspa-0.2-jack
systemctl --user enable pipewire.socket
systemctl --user start pipewire.socket

standalone node

Refer tograysky2/kodi-standalone-service with several changes:

  1. x86/init/kodi-gbm.service line 14: ExecStart=/usr/local/bin/kodi-standalone
  2. x86/init/sysusers.conf uncomment line 22, 26, comment line 9, add m kodi input and m kodi plugdev