Media & Entertainment AWS

Karaoke Eternal 2.1.0 on AWS User Guide

| Product: Karaoke Eternal 2.1.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Karaoke Eternal 2.1.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg media servers listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Karaoke Eternal is an open source karaoke party server. You put your own karaoke files (MP3+CDG pairs, ZIP files of those pairs, or MP4 videos) in a folder, and everyone at the party uses the browser on their phone to search the library and add songs to a shared queue. A laptop or TV browser opens the player, which shows the lyrics and plays each singer's song in turn. The queue, the player and every phone stay in sync live.

What cloudimg adds:

  • HTTPS from the first boot. nginx serves Karaoke Eternal on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. The live queue sync runs over secure WebSockets (wss) on the same port.
  • Your own administrator and room passwords. First boot creates the administrator kadmin with a password unique to your instance, sets a separate password on Room 1, and writes both to /root/karaoke-eternal-credentials.txt, readable by root only. No password is baked into the image.
  • No open first run sign up. Karaoke Eternal normally lets the first visitor create the administrator account. On this image first boot has already done that, so the setup page is closed: nobody else can register as an administrator. New accounts are off until you allow them for a room (Step 8), and even then only with the room password.
  • The app on loopback. Karaoke Eternal runs as its own unprivileged karaoke user and its port 8080 accepts connections from the instance itself only. nginx is the only service on the network, and it passes the real client address to Karaoke Eternal, which uses it for its sign in rate limits.
  • Hardened defaults. The session cookie is marked Secure, a browser on another site cannot sign in or open a WebSocket to your server, the app and nginx are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • A lean image. Node.js 24 comes from NodeSource with its signing key pinned to a known fingerprint. The build tools, the development dependencies, the npm cache, the git history and ffmpeg (used only at build time to test playback) are removed before the image is captured.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

An empty library by design. cloudimg ships no songs and no sample media of any kind. Step 7 shows how to add your own files.

What is on the image: Karaoke Eternal 2.1.0 in /opt/karaoke-eternal (built by cloudimg from the upstream v2.1.0 release, production dependencies only), Node.js 24 from NodeSource, nginx, SQLite, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
  • A security group that allows:
  • TCP 22 (SSH) from your own networks only
  • TCP 443 (the web app) from the networks your singers use
  • TCP 80 (redirects to HTTPS) from the same sources as 443

Karaoke Eternal is light: an m5.large serves a large party. The library scan and the database live on the root volume, so size the volume for your media collection (or attach a second volume for it, see Maintenance).

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Karaoke Eternal 2.1.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=karaoke}]'

Step 2: Connect to your instance

Version SSH user
Karaoke Eternal 2.1.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and the two passwords, creates the administrator, then starts Karaoke Eternal and nginx:

systemctl is-active karaoke-eternal nginx
sudo test -f /var/lib/cloudimg/karaoke-eternal-firstboot.done && echo FIRSTBOOT_DONE
node --version

Both services are active, the first boot marker is present and Node.js reports its version:

active
active
FIRSTBOOT_DONE
v24.22.2

nginx is the only service on public addresses (80 and 443). Karaoke Eternal's own port 8080 is open to the instance itself only, enforced by systemd for that service:

sudo ss -lntpH | awk '{split($6, a, "\""); print $4, a[2]}' | LC_ALL=C sort
for p in IPAddressAllow IPAddressDeny; do echo "$p=$(systemctl show karaoke-eternal -p $p --value | tr ' ' '\n' | LC_ALL=C sort | xargs)"; done
*:8080 MainThread
0.0.0.0:22 sshd
0.0.0.0:443 nginx
0.0.0.0:80 nginx
127.0.0.53%lo:53 systemd-resolve
127.0.0.54:53 systemd-resolve
[::]:22 sshd
[::]:443 nginx
[::]:80 nginx
IPAddressAllow=127.0.0.0/8 ::1/128
IPAddressDeny=0.0.0.0/0 ::/0

MainThread is Node.js, which names its main thread that. Although port 8080 shows as *, systemd drops every packet to it that does not come from the instance itself.

Step 4: Get your URL and passwords

sudo cat /root/karaoke-eternal-credentials.txt

The file is readable by root only:

# Karaoke Eternal 2.1.0 on Ubuntu 24.04 - generated on first boot by karaoke-eternal-firstboot.service
# These values are unique to this instance; none was baked into the image. Keep this file secret.
#
# The image contained no database: this instance created its own (with a fresh session signing key), the
# administrator below was created here through the product's own one-shot setup, and that setup is now closed.
# Guests join Room 1 from their phones with the room password and pick their own singer name.
#
# YOUR MEDIA LIBRARY IS EMPTY BY DESIGN: cloudimg ships no karaoke tracks of any kind. Copy your OWN files
# (MP3+CDG pairs, ZIPs of those pairs, or MP4 videos) into KARAOKE_ETERNAL_MEDIA_DIR, then sign in as the
# administrator and choose Preferences -> Media Folders -> Refresh.
#
KARAOKE_ETERNAL_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
KARAOKE_ETERNAL_ADMIN_USER=kadmin
KARAOKE_ETERNAL_ADMIN_PASSWORD=<KARAOKE_ETERNAL_ADMIN_PASSWORD>
KARAOKE_ETERNAL_ROOM=Room 1
KARAOKE_ETERNAL_ROOM_PASSWORD=<KARAOKE_ETERNAL_ROOM_PASSWORD>
KARAOKE_ETERNAL_MEDIA_DIR=/srv/karaoke

# Self-signed certificate minted on first boot:  openssl x509 -in /etc/ssl/karaoke-eternal/karaoke-eternal.crt -noout -fingerprint -sha256
# Change the name it is served as:              sudo karaoke-eternal-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/karaoke-eternal-2-1-0-on-ubuntu-24-04-aws/
  • KARAOKE_ETERNAL_URL is the address to open in a browser.
  • KARAOKE_ETERNAL_ADMIN_USER and KARAOKE_ETERNAL_ADMIN_PASSWORD sign you in as the administrator.
  • KARAOKE_ETERNAL_ROOM_PASSWORD is what you give your singers so they can join Room 1.

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To use your own DNS name, see Step 9. When you need to share the file, for example in a support request, print it with the passwords masked:

sudo sed -E 's/^(KARAOKE_ETERNAL_[A-Z_]*PASSWORD=).*/\1********/' /root/karaoke-eternal-credentials.txt | grep -E '^KARAOKE_ETERNAL_'

Step 5: Check the server from the command line

Port 80 redirects to HTTPS, the health check is open for load balancers, and the certificate nginx serves is the one first boot generated on this instance:

H=$(sudo grep '^KARAOKE_ETERNAL_URL=' /root/karaoke-eternal-credentials.txt | sed -E 's#^KARAOKE_ETERNAL_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http://   -> %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'healthz   -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -fingerprint -sha256 | cut -d= -f2 > /tmp/served.fp
sudo openssl x509 -in /etc/ssl/karaoke-eternal/karaoke-eternal.crt -noout -fingerprint -sha256 | cut -d= -f2 | cmp -s - /tmp/served.fp && echo "certificate on 443 = this instance's own"
rm -f /tmp/served.fp
http://   -> 301
healthz   -> HTTP 200
certificate on 443 = this instance's own

The setup page is closed, and an anonymous request for the account list is refused:

H=$(sudo grep '^KARAOKE_ETERNAL_URL=' /root/karaoke-eternal-credentials.txt | sed -E 's#^KARAOKE_ETERNAL_URL=https://([^/]+)/.*#\1#')
curl -sk -o /dev/null -w 'POST /api/setup  -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" \
  -H 'Content-Type: application/json' --data '{"username":"someone","newPassword":"not-a-real-one","newPasswordConfirm":"not-a-real-one","name":"x"}' "https://$H/api/setup"
curl -sk -o /dev/null -w 'GET /api/users   -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/users"
POST /api/setup  -> HTTP 403
GET /api/users   -> HTTP 401

The administrator signs in over HTTPS and receives a Secure session cookie. The password is passed to curl on standard input, so it never appears in the process list or your shell history:

sudo bash -c '
  F=/root/karaoke-eternal-credentials.txt
  P=$(grep "^KARAOKE_ETERNAL_ADMIN_PASSWORD=" $F | cut -d= -f2-)
  H=$(grep "^KARAOKE_ETERNAL_URL=" $F | sed -E "s#^KARAOKE_ETERNAL_URL=https://([^/]+)/.*#\1#")
  printf "{\"username\":\"kadmin\",\"password\":\"%s\"}" "$P" | curl -sk -o /dev/null -D - --resolve "$H:443:127.0.0.1" \
    -H "Origin: https://$H" -H "Content-Type: application/json" --data-binary @- "https://$H/api/login" \
    | tr -d "\r" | grep -iE "^HTTP/|^set-cookie: keToken" | sed -E "s/keToken=[^;]*/keToken=<session>/; s/ +$//"
'
HTTP/2 200
set-cookie: keToken=<session>; path=/; samesite=lax; secure; httponly

Step 6: Sign in from a browser

Open KARAOKE_ETERNAL_URL. The certificate is self signed, so the browser warns once; check that the SHA-256 fingerprint it shows matches openssl x509 -in /etc/ssl/karaoke-eternal/karaoke-eternal.crt -noout -fingerprint -sha256 on the instance, then continue. To avoid the warning for everyone, use your own DNS name and a public certificate (Step 9).

With Room 1 selected, enter the room password, then sign in with kadmin and the administrator password.

The Karaoke Eternal sign in page served over HTTPS, with fields for the username, the password and the room

The library is empty until you add your own songs:

The Karaoke Eternal library view after signing in as the administrator

Under Account the administrator manages rooms, media folders, players and users. /srv/karaoke is already registered as the media folder:

The Karaoke Eternal account page with the media folder /srv/karaoke registered

Step 7: Add your songs

Copy your own karaoke files into /srv/karaoke. Karaoke Eternal reads MP3+CDG pairs (the two files share a name), ZIP files that contain such a pair, and MP4 videos. Subfolders are fine. From your computer:

rsync -av --progress ./my-karaoke/ ubuntu@<public-ip>:/srv/karaoke/

The ubuntu user is a member of the karaoke group, which owns the folder, so no sudo is needed. Then sign in as the administrator, open Account, and under Preferences, Media Folders choose the refresh icon beside srv/karaoke/ (or Scan Folders). The library fills as the scan runs. To have files you add later appear on their own, choose the info icon beside the folder and tick Watch folder.

Step 8: Run a party

  • Let singers join. By default nobody can create their own account. For a party, sign in as the administrator, open Account, choose Room 1 under Rooms, open Users, tick Allow new guests (and Allow new standard users if regulars should keep a named account and their starred songs), and choose Update Room. You can also create accounts yourself under Users, Create User.
  • Singers then open KARAOKE_ETERNAL_URL on their phones, choose Room 1, enter the room password and join with a name of their own. Without the room password they cannot join or create an account.
  • The player runs in a browser on the computer or TV connected to your speakers: sign in as the administrator and choose Launch Player in the banner at the top of the page. It plays the queue in order and shows the lyrics.
  • The queue updates live on every phone over the secure WebSocket connection.

You can rename Room 1, change its password, open more rooms or close a room under Account, Rooms. Keep /root/karaoke-eternal-credentials.txt up to date if you change the passwords; first boot runs only once, so it never overwrites your changes.

Step 9: Use your own DNS name and certificate

Point a DNS record at the instance (an Elastic IP keeps the address stable), then tell the instance its name. This regenerates the self signed certificate for that name, updates nginx and the credentials file, and reloads nginx:

sudo karaoke-eternal-set-hostname karaoke.your-domain.example

To use a certificate from a public CA instead, replace /etc/ssl/karaoke-eternal/karaoke-eternal.crt (full chain) and /etc/ssl/karaoke-eternal/karaoke-eternal.key (mode 0600) and run sudo systemctl reload nginx.

Step 10: Security checks

Karaoke Eternal and nginx are blocked from the instance metadata service, so nothing they fetch can reach your instance's credentials. Karaoke Eternal can talk to the instance itself only, and it trusts the forwarded client address from nginx on loopback only:

for u in nginx karaoke-eternal; do echo "$u: $(systemctl show $u -p IPAddressDeny --value | tr ' ' '\n' | LC_ALL=C sort | xargs)"; done
systemctl show karaoke-eternal -p Environment --value | tr ' ' '\n' | grep KES_PROXY_TRUST
nginx: 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
karaoke-eternal: 0.0.0.0/0 ::/0
KES_PROXY_TRUST=loopback

The NodeSource repository is trusted through a keyring that holds exactly one key, with the fingerprint cloudimg pinned at build time, and apt takes only the nodejs package from it:

gpg --show-keys --with-colons /etc/apt/keyrings/nodesource.gpg 2>/dev/null | awk -F: '$1=="pub"{n++} $1=="fpr" && !f {f=$10} END {print n" primary key, "f}'
grep -E '^(URIs|Signed-By):' /etc/apt/sources.list.d/nodesource.sources
1 primary key, 6F71F525282841EEDAF851B42F59B5F99B1BE0B4
URIs: https://deb.nodesource.com/node_24.x
Signed-By: /etc/apt/keyrings/nodesource.gpg

No build tool, ffmpeg, development dependency or git history ships on the image:

for t in gcc make ffmpeg; do command -v $t >/dev/null && echo "$t present" || echo "$t absent"; done
dpkg -l 'ffmpeg' 'libavcodec*' 2>/dev/null | grep -c '^ii' || true
ls /opt/karaoke-eternal/node_modules | grep -cxE 'webpack|typescript|eslint|vitest' || true
test -e /opt/karaoke-eternal/.git && echo ".git present" || echo ".git absent"
gcc absent
make absent
ffmpeg absent
0
0
.git absent

Maintenance

  • Service: karaoke-eternal.service runs node /opt/karaoke-eternal/build/server/main.js as the karaoke user. Its log is in the journal: journalctl -u karaoke-eternal.
  • Data: the database (accounts, rooms, queue and library index) is in /var/lib/karaoke-eternal/data/; your media is in /srv/karaoke. Those two folders are the only state worth backing up. To keep a large collection on its own EBS volume, mount it at /srv/karaoke (owner karaoke, group karaoke, mode 2775) and refresh the media folder.
  • nginx: the site is /etc/nginx/sites-available/cloudimg-karaoke-eternal, with real client addresses in /var/log/nginx/. Check changes with sudo nginx -t and apply them with sudo systemctl reload nginx.
  • Security updates: unattended security upgrades are on and cover Node.js (from NodeSource), nginx and the operating system. Reboot when a new kernel is installed. Karaoke Eternal itself is updated by a new version of this image.

Licences

Karaoke Eternal is developed by the Karaoke Eternal project and licensed under the ISC licence; its licence text ships in /opt/karaoke-eternal/LICENSE. Node.js is licensed under the MIT licence and nginx under the 2 clause BSD licence. You are responsible for holding the rights to the karaoke media you add.

Support

cloudimg provides 24/7/365 expert technical support for this image. Contact support@cloudimg.co.uk. This image is provided by cloudimg and is not affiliated with or endorsed by the Karaoke Eternal project.