SQL Server 2019 Express in Proxmox LXC (Ubuntu 20.04) – Installation & Backup auf NAS
Ziele:
- MSSQL 2019 Express im LXC installieren
- SQL-Tools (
sqlcmd) installieren - NAS-Freigabe per CIFS einbinden (ohne Automount)
- Backup-Script mit Zeitplan: Mo–Fr 09/11/13/15/17 Uhr & täglich 20:00 Uhr
- Um 20:00 Uhr vor dem Backup:
DBCC CHECKDB; Backup läuft immer weiter - Bei CHECKDB-Fehlern: Suffix
__CHECKDB_FAILim Dateinamen - Wichtig: SQL Express unterstützt keine Backup-Kompression → nicht verwendet
1) LXC-Container vorbereiten
Container mit Ubuntu 20.04 erstellen und danach folgende Optionen in /etc/pve/lxc/CTID.conf ergänzen (CTID ersetzen):
lxc.apparmor.profile: unconfined
lxc.cap.drop:
lxc.mount.auto: sys:rw
lxc.cgroup.devices.allow: a
2) MSSQL 2019 Express installieren
sudo apt update
sudo apt install -y curl gnupg ca-certificates apt-transport-https
sudo install -d -m 0755 /usr/share/keyrings
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/microsoft.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/ubuntu/20.04/mssql-server-2019 focal main" \
| sudo tee /etc/apt/sources.list.d/mssql-server.list
sudo apt update
sudo apt install -y mssql-server
sudo /opt/mssql/bin/mssql-conf setup
Im Setup die Edition Express wählen und SA-Passwort setzen. Status prüfen:
systemctl status mssql-server
3) SQL Tools installieren (sqlcmd)
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list \
| sudo tee /etc/apt/sources.list.d/msprod.list
sudo apt update
sudo apt install -y mssql-tools unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' | sudo tee -a ~/.bashrc
source ~/.bashrc
# Test
sqlcmd -S localhost -U SA -P "DEIN_PASSWORT" -Q "SELECT @@VERSION"
4) NAS-Freigabe (CIFS) einbinden – ohne Automount
Konfiguration so, dass der LXC auch bootet, wenn das NAS nicht erreichbar ist: nofail, soft, x-systemd.device-timeout=10 (kein x-systemd.automount).
sudo apt install -y cifs-utils
sudo mkdir -p /mnt/sqlbackup
# Zugangsdatendatei
sudo bash -c 'cat > /root/.smbcred <<EOF
username=NASUSER
password=NASPASS
domain=WORKGROUP
EOF'
sudo chmod 600 /root/.smbcred
# fstab-Eintrag (IP/Share anpassen)
echo "//192.168.1.100/sqlbackup /mnt/sqlbackup cifs credentials=/root/.smbcred,iocharset=utf8,file_mode=0777,dir_mode=0777,nofail,soft,x-systemd.device-timeout=10 0 0" \
| sudo tee -a /etc/fstab
# Testmount
sudo mount -a
ls -la /mnt/sqlbackup
5) Backup-Script mit integriertem CHECKDB (20:00)
Script speichert alle User-DBs (außer master, model, msdb, tempdb) als .bak. Um 20:00 wird vorab DBCC CHECKDB pro DB ausgeführt. Bei Fehlern wird der Backupdateiname mit __CHECKDB_FAIL markiert. SQL Express → keine Kompression.
Datei: /usr/local/bin/mssql_backup.sh
sudo bash -c 'cat > /usr/local/bin/mssql_backup.sh << "EOF"
#!/bin/bash
set -euo pipefail
# -----------------------------
# MSSQL Backup + (20:00) CHECKDB mit Fail-Suffix (SQL Express)
# -----------------------------
SA_USER="SA"
SA_PASS="DEIN_SA_PASSWORT" # <— anpassen
SQLHOST="localhost"
DEST="/mnt/sqlbackup/mssql"
RETENTION_DAYS=7
LOG_CHECKDB="/var/log/mssql_checkdb.log"
SQLCMD="/opt/mssql-tools/bin/sqlcmd"
SQL_TIMEOUT=900 # 15 Minuten Timeout je sqlcmd
# Prevent overlapping runs
exec 9>/var/lock/mssql_backup.lock
flock -n 9 || { echo "Backup already running, exiting."; exit 0; }
# Ensure paths
mkdir -p "$DEST" "$(dirname "$LOG_CHECKDB")"
# sqlcmd detection
[ -x "$SQLCMD" ] || SQLCMD="$(command -v sqlcmd || true)"
if [ -z "${SQLCMD}" ]; then
echo "FATAL: sqlcmd not found (install mssql-tools)." >&2
exit 3
fi
ts(){ date "+%Y-%m-%d %H:%M:%S"; }
# --- DB-Liste holen (einmalig), CR entfernen, leere/dupl. raus ---
RAW_DBLIST="$(
timeout "$SQL_TIMEOUT" "$SQLCMD" -S "$SQLHOST" -U "$SA_USER" -P "$SA_PASS" \
-Q "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE name NOT IN ('master','model','msdb','tempdb');" \
-h -1 -W || true
)"
SANITIZED="$(printf "%s\n" "$RAW_DBLIST" | tr -d "\r" | sed "/^[[:space:]]*$/d" | sort -u)"
if [ -z "$SANITIZED" ]; then
echo "[$(ts)] No user databases found. Nothing to do."
exit 0
fi
# In Array laden
mapfile -t DBS < <(printf "%s\n" "$SANITIZED")
# --- 20:00 CHECKDB (before backups) ---
declare -A CHECKDB_STATUS
HOUR_NOW="$(date +%H)"
if [ "$HOUR_NOW" = "20" ]; then
echo "[$(ts)] ===== DBCC CHECKDB (pre-backup) start =====" | tee -a "$LOG_CHECKDB"
for DB in "${DBS[@]}"; do
DB_TRIM="$(echo "$DB" | xargs)"
[ -z "$DB_TRIM" ] && continue
echo "[$(ts)] CHECKDB on [$DB_TRIM]..." | tee -a "$LOG_CHECKDB"
if ! timeout "$SQL_TIMEOUT" "$SQLCMD" -S "$SQLHOST" -U "$SA_USER" -P "$SA_PASS" \
-Q "DBCC CHECKDB('$DB_TRIM') WITH NO_INFOMSGS, ALL_ERRORMSGS;" \
-b -V 16 >>"$LOG_CHECKDB" 2>&1; then
echo "[$(ts)] ERROR in CHECKDB for [$DB_TRIM]" | tee -a "$LOG_CHECKDB"
CHECKDB_STATUS["$DB_TRIM"]="FAIL"
else
echo "[$(ts)] OK: [$DB_TRIM]" | tee -a "$LOG_CHECKDB"
CHECKDB_STATUS["$DB_TRIM"]="OK"
fi
done
echo "[$(ts)] ===== DBCC CHECKDB finished =====" | tee -a "$LOG_CHECKDB"
fi
# --- Backups ---
DATE_STR="$(date +%F_%H-%M)"
echo "[$(ts)] ===== Backups start ($DATE_STR) ====="
for DB in "${DBS[@]}"; do
DB_TRIM="$(echo "$DB" | xargs)"
[ -z "$DB_TRIM" ] && continue
SUFFIX=""
if [ "$HOUR_NOW" = "20" ] && [ "${CHECKDB_STATUS[$DB_TRIM]:-OK}" = "FAIL" ]; then
SUFFIX="__CHECKDB_FAIL"
fi
OUT_FILE="$DEST/${DB_TRIM}_${DATE_STR}${SUFFIX}.bak"
echo "[$(ts)] Backup DB: $DB_TRIM -> $OUT_FILE"
# SQL Express: keine Kompression
timeout "$SQL_TIMEOUT" "$SQLCMD" -S "$SQLHOST" -U "$SA_USER" -P "$SA_PASS" \
-Q "BACKUP DATABASE [$DB_TRIM] TO DISK = N'${OUT_FILE//\'/\'\'}' WITH INIT, STATS=5;"
done
# Cleanup
find "$DEST" -type f -name "*.bak" -mtime +$RETENTION_DAYS -delete || true
echo "[$(ts)] ===== Backups finished ====="
EOF'
sudo chmod +x /usr/local/bin/mssql_backup.sh
6) Systemd Service & Timer einrichten
Service:
sudo bash -c 'cat > /etc/systemd/system/mssql-backup.service << "EOF"
[Unit]
Description=MSSQL backup + daily CHECKDB (20:00)
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mssql_backup.sh
EOF'
Timer (Mo–Fr: 09/11/13/15/17 Uhr & täglich 20:00 Uhr):
sudo bash -c 'cat > /etc/systemd/system/mssql-backup.timer << "EOF"
[Unit]
Description=Run MSSQL backups on schedule
[Timer]
OnCalendar=Mon..Fri *-*-* 09,11,13,15,17:00:00
OnCalendar=*-*-* 20:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF'
sudo systemctl daemon-reload
sudo systemctl enable --now mssql-backup.timer
Manuell testen:
systemctl start mssql-backup.service
journalctl -u mssql-backup.service -n 100 --no-pager
tail -n 100 /var/log/mssql_checkdb.log
7) Wichtige Hinweise
- SQL Express unterstützt keine Backup-Kompression – daher nicht im Script.
DBCC CHECKDBrepariert nicht automatisch. Bei Fehlern wird die Backupdatei mit__CHECKDB_FAILmarkiert; Backup läuft weiter./etc/fstabEintrag ist ohne Automount, startet den LXC aber auch bei nicht erreichbarem NAS (nofail,soft,x-systemd.device-timeout=10).- Aufbewahrung ist 7 Tage (per
find ... -mtime +7im Script) – anpassbar.
8) Troubleshooting
- Hängt ein Lauf? → Prüfe Prozesse:
ps -ef | grep sqlcmd - Timer läuft nicht? →
systemctl status mssql-backup.timer - NAS nicht gemountet? →
mount -a, Pfad prüfen:ls -la /mnt/sqlbackup - CHECKDB-Log:
/var/log/mssql_checkdb.log
