
Move your MidnightStreamer panel database to Xtream-Masters or another panel: channels, movies, series, lines, resellers, bouquets, EPG and more. Run the backup script on this page first, it prints your database size, then pick the plan that covers it (Up to 1...
Check your database size first
The price depends on how big your MidnightStreamer database is. Run the backup now, the script prints the size, then pick the matching plan.
Paste the script into SSH and press Enter. It reads your own panel config, so there is nothing to fill in. The last lines print the file size and a link.
cat > /root/xm-backup.sh <<'XMEOF'
#!/bin/bash
# Xtream-Masters - MidnightStreamer backup helper.
# Finds your panel database on its own, so there is nothing to fill in.
# bash /root/xm-backup.sh normal run
# bash /root/xm-backup.sh --password 'xxxx' if MySQL root needs a password
# bash /root/xm-backup.sh --stop stop the temporary download server
set -u
say() { printf '%s\n' "$*"; }
die() { printf '\n [X] %s\n\n' "$*" >&2; exit 1; }
[ "$(id -u)" = "0" ] || die "Run this as root."
SHARE=/root/xm-share
if [ "${1:-}" = "--stop" ]; then
if [ -f "$SHARE/pid" ] && kill "$(cat "$SHARE/pid")" 2>/dev/null; then say " [*] temporary download server stopped"; else say " [*] nothing to stop"; fi
rm -rf "$SHARE"
exit 0
fi
command -v mysql >/dev/null 2>&1 || die "mysql client is not installed on this server."
command -v mysqldump >/dev/null 2>&1 || die "mysqldump is not installed on this server."
PW=""
[ "${1:-}" = "--password" ] && PW="${2:-}"
# ---- 1. connect ----------------------------------------------------------
# MidnightStreamer's MySQL root works over the local socket with no password.
# A --password is only needed if that was changed by hand.
M() { if [ -n "$PW" ]; then MYSQL_PWD="$PW" mysql -u root "$@"; else mysql -u root "$@"; fi; }
D() { if [ -n "$PW" ]; then MYSQL_PWD="$PW" mysqldump -u root "$@"; else mysqldump -u root "$@"; fi; }
if ! M -N -e 'SELECT 1' >/dev/null 2>&1; then
say ""
say " [X] Could not log in to MySQL as root."
say " If your MySQL root has a password, run:"
say " bash /root/xm-backup.sh --password 'your-mysql-root-password'"
die "Nothing has been changed."
fi
# ---- 2. find the panel database -----------------------------------------
DBN=""
for d in $(M -N -e "SELECT DISTINCT table_schema FROM information_schema.tables WHERE table_name IN ('streams','lines','mags','resellers','bouquets_sids') GROUP BY table_schema HAVING COUNT(DISTINCT table_name)=5 ORDER BY (table_schema='midnight_iptv') DESC, table_schema" 2>/dev/null); do
DBN="$d"; break
done
[ -n "$DBN" ] || die "Could not find a MidnightStreamer database on this server."
TABLES=$(M -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DBN'")
say " [1/5] database : $DBN ($TABLES tables)"
# ---- 3. dump -------------------------------------------------------------
TOKEN=$(head -c 9 /dev/urandom | od -An -tx1 | tr -d ' \n')
NAME="xm-$TOKEN.sql"
TMP="/root/$NAME"
IGN=""
for t in claims e2_events epg_channels epg_data errors_log lids_sids line_actions_log lines_error_log lines_log lines_log_closed logins_log mag_events messages prepare server_info syncs tickets; do IGN="$IGN --ignore-table=$DBN.$t"; done
say " [2/5] writing : $TMP"
say " this can take a few minutes on a large panel, please wait"
# shellcheck disable=SC2086
D --single-transaction --quick --skip-lock-tables --default-character-set=utf8mb4 \
--hex-blob --no-tablespaces --skip-triggers --routines=0 --events=0 $IGN "$DBN" > "$TMP" \
|| die "mysqldump failed. Check free disk space with 'df -h' and run this again."
[ -s "$TMP" ] || die "The backup came out empty."
tail -c 400 "$TMP" | grep -q 'Dump completed' \
|| die "The backup is incomplete - it stopped early. Check free disk space with 'df -h' and run this again."
chmod 644 "$TMP"
SIZE=$(du -h "$TMP" | cut -f1)
say " [3/5] verified : complete dump, $SIZE"
# ---- 4. make it downloadable -------------------------------------------
# First choice: the panel's own web folder, served by its nginx. That root
# is read from the running nginx config and TESTED with a real download,
# because on some installs the configured folder is not the one that exists.
IP=$(curl -fsS -m 8 https://api.ipify.org 2>/dev/null)
[ -n "$IP" ] || IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[ -n "$IP" ] || IP="your-server-ip"
works() { # works <url> -> 0 when the first bytes of the file come back
local got
got=$(curl -fsS -m 10 -r 0-15 "$1" 2>/dev/null | head -c 16)
[ -n "$got" ] && [ "$got" = "$(head -c 16 "$TMP")" ]
}
LINK=""
ROOTS=$(grep -rhoE '^\s*root\s+[^;]+' /home/*/iptv_midnight_streamer/nginx/conf/ /etc/nginx/ 2>/dev/null | awk '{print $2}' | sort -u)
PORTS=$(grep -rhoE '^\s*listen\s+[0-9]+' /home/*/iptv_midnight_streamer/nginx/conf/ /etc/nginx/ 2>/dev/null | awk '{print $2}' | sort -u)
for r in $ROOTS /var/www/html /usr/share/nginx/html; do
[ -d "$r" ] || continue
cp -f "$TMP" "$r/$NAME" 2>/dev/null || continue
chmod 644 "$r/$NAME"
for p in $PORTS 80 8080; do
if works "http://127.0.0.1:$p/$NAME"; then
LINK="http://$IP:$p/$NAME"
[ "$p" = "80" ] && LINK="http://$IP/$NAME"
break 2
fi
done
rm -f "$r/$NAME"
done
# Fallback: a small temporary web server that serves only this one file.
if [ -z "$LINK" ]; then
command -v python3 >/dev/null 2>&1 || die "No web folder worked and python3 is not installed. Upload $TMP to any public web space and paste that link."
[ -f "$SHARE/pid" ] && kill "$(cat "$SHARE/pid")" 2>/dev/null
rm -rf "$SHARE"; mkdir -p "$SHARE"; cp -f "$TMP" "$SHARE/$NAME"
for p in 80 8080 8443 443; do
# Only this one server is ever stopped again, by the pid written here.
( cd "$SHARE" && setsid nohup python3 -m http.server "$p" --bind 0.0.0.0 >/dev/null 2>&1 </dev/null & echo $! > "$SHARE/pid" )
sleep 2
if works "http://127.0.0.1:$p/$NAME"; then
LINK="http://$IP:$p/$NAME"
[ "$p" = "80" ] && LINK="http://$IP/$NAME"
break
fi
kill "$(cat "$SHARE/pid")" 2>/dev/null; rm -f "$SHARE/pid"
done
[ -n "$LINK" ] || die "Could not start a download server (ports 80, 8080, 8443 and 443 are all in use). Upload $TMP to any public web space and paste that link."
say " [4/5] shared : temporary download server (stop it later with: bash /root/xm-backup.sh --stop)"
else
say " [4/5] shared : through your panel's web server"
fi
rm -f "$TMP"
say " [5/5] done"
say ""
say " ============================================================"
say " Backup size: $SIZE"
say ""
say " Paste this link into your order page:"
say ""
say " $LINK"
say ""
say " When your migration has finished, remove the file:"
say " bash /root/xm-backup.sh --stop ; rm -f /home/*/iptv_midnight_streamer/wwwdir/xm-*.sql /var/www/html/xm-*.sql"
say " ============================================================"
XMEOF
bash /root/xm-backup.shKeep the link the script prints. You paste it on your order page after payment and the migration starts on its own.
The size you buy is checked automatically when you submit your backup.
Bought the smaller plan and your backup turned out bigger? Your order page prices the difference and you carry on. No support ticket.
| Database size | Price | What is included |
|---|---|---|
| Up to 1 GB | $99 | Standard migration, delivered automatically from your client area. |
| 1 GB to 5 GB | $149 | Larger databases. Pick this plan at checkout, nothing else changes. |
| Category | Details | Status |
|---|---|---|
| Live channels & radio | All live streams with every source URL, server assignment, EPG mapping, channel number and ordering | Migrated |
| Movies | Full VOD library: local files and direct or redirected URLs, container format, TMDB data | Migrated |
| Series & episodes | Series, seasons and every episode with its season and episode number | Migrated |
| TMDB metadata | Posters, backdrops, cast, genres, ratings, trailers and descriptions for movies and series | Migrated |
| Channel order | The order of channels inside your bouquets is kept | Migrated |
| Lines (users) | Subscriber lines with passwords, expiry, bouquets, connection limits, output formats, IP, ISP and user-agent locks, notes | Migrated |
| Reseller accounts | Resellers, admins, their groups, credits and ownership tree | Migrated, same passwords |
| Reseller credit logs | Full credit history | Migrated |
| Bouquets | Bouquets with their channel, movie and series assignments | Migrated |
| Categories | Live, movie and series categories with parents, order and adult flag | Migrated |
| Packages | Subscription plans with duration, credits, bouquets, groups and outputs | Migrated |
| MAG & Enigma2 devices | Registered MAG / STB devices with their MAC and settings, Enigma2 boxes | Migrated |
| Streaming servers | Server records with IP, domain, ports and limits | Migrated |
| EPG | Guide sources with their XMLTV links and every channel mapping, re-attached to your channels | Migrated |
| Blocked IPs & user agents | Your block lists | Migrated |
| Watch folders | Folder watch paths and their targets | Migrated |
| Transcoding profiles | Profile names (settings are panel-specific and must be set again) | Partially |
| Activity, connection & EPG cache tables | Line logs, login logs, EPG data cache, tickets, messages | Not migrated, excluded on purpose to keep the database small and fast |