mirror of
https://github.com/awalol/virtual-dsm-proxmox-qemu.git
synced 2025-09-26 19:11:14 +08:00
342 lines
9.5 KiB
Bash
342 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
STORAGE=$(pwd)/workdir
|
||
COUNTRY="CN"
|
||
DEBUG="Y"
|
||
ARCH="amd64"
|
||
mkdir -p $STORAGE
|
||
|
||
html(){
|
||
echo "html: $1"
|
||
}
|
||
|
||
error(){
|
||
printf "%b%s%b \E[1;34m❯ \E[1;31m ERROR: $1 \E[0m\n"
|
||
}
|
||
|
||
info(){
|
||
printf "%b%s%b \E[1;34m❯ \E[1;36m Info: $1 \E[0m\n"
|
||
}
|
||
|
||
formatBytes() {
|
||
local result
|
||
result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
|
||
local unit="${result//[0-9. ]}"
|
||
result="${result//[a-zA-Z ]/}"
|
||
if [[ "${2:-}" == "up" ]]; then
|
||
if [[ "$result" == *"."* ]]; then
|
||
result="${result%%.*}"
|
||
result=$((result+1))
|
||
fi
|
||
else
|
||
if [[ "${2:-}" == "down" ]]; then
|
||
result="${result%%.*}"
|
||
fi
|
||
fi
|
||
echo "$result $unit"
|
||
return 0
|
||
}
|
||
|
||
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||
|
||
if [ -f "$STORAGE/dsm.ver" ]; then
|
||
BASE=$(<"$STORAGE/dsm.ver")
|
||
BASE="${BASE//[![:print:]]/}"
|
||
[ -z "$BASE" ] && BASE="DSM_VirtualDSM_69057"
|
||
else
|
||
# Fallback for old installs
|
||
BASE="DSM_VirtualDSM_42962"
|
||
fi
|
||
|
||
FN="boot.pat"
|
||
DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||
|
||
if [ -d "$DIR" ]; then
|
||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||
if [[ ! -s "$STORAGE/$BASE.boot.img" ]] || [[ ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||
error "The bind $DIR maps to a file that does not exist!" && exit 65
|
||
fi
|
||
fi
|
||
|
||
FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||
[ ! -s "$FILE" ] && FILE=$(find "$STORAGE" -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||
[ -s "$FILE" ] && BASE="DSM_VirtualDSM" && URL="file://$FILE"
|
||
|
||
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||
BASE=$(basename "$URL" .pat)
|
||
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||
BASE=$(basename "${URL%%\?*}" .pat)
|
||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
||
fi
|
||
if [[ "${URL,,}" != "http"* ]] && [[ "${URL,,}" != "file:"* ]] ; then
|
||
[ ! -s "$STORAGE/$BASE.pat" ] && error "Invalid URL: $URL" && exit 65
|
||
URL="file://$STORAGE/$BASE.pat"
|
||
fi
|
||
fi
|
||
|
||
if [[ -s "$STORAGE/$BASE.boot.img" ]] && [[ -s "$STORAGE/$BASE.system.img" ]]; then
|
||
return 0 # Previous installation found
|
||
fi
|
||
|
||
html "Please wait while Virtual DSM is being installed..."
|
||
|
||
DL=""
|
||
DL_CHINA="https://cndl.synology.cn/download/DSM"
|
||
DL_GLOBAL="https://global.synologydownload.com/download/DSM"
|
||
|
||
[[ "${URL,,}" == *"cndl.synology"* ]] && DL="$DL_CHINA"
|
||
[[ "${URL,,}" == *"global.synology"* ]] && DL="$DL_GLOBAL"
|
||
|
||
if [ -z "$DL" ]; then
|
||
[ -z "$COUNTRY" ] && setCountry
|
||
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
||
[[ "${COUNTRY^^}" == "CN" ]] && DL="$DL_CHINA" || DL="$DL_GLOBAL"
|
||
fi
|
||
|
||
if [ -z "$URL" ]; then
|
||
URL="$DL/release/7.2.2/72806/DSM_VirtualDSM_72806.pat"
|
||
fi
|
||
|
||
if [ ! -s "$FILE" ]; then
|
||
BASE=$(basename "${URL%%\?*}" .pat)
|
||
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
|
||
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
||
fi
|
||
|
||
if [[ "$URL" != "file://$STORAGE/$BASE.pat" ]]; then
|
||
rm -f "$STORAGE/$BASE.pat"
|
||
fi
|
||
|
||
rm -f "$STORAGE/$BASE.agent"
|
||
rm -f "$STORAGE/$BASE.boot.img"
|
||
rm -f "$STORAGE/$BASE.system.img"
|
||
|
||
# Check filesystem
|
||
FS=$(stat -f -c %T "$STORAGE")
|
||
|
||
if [[ "${FS,,}" == "overlay"* ]]; then
|
||
info "Warning: the filesystem of $STORAGE is OverlayFS, this usually means it was binded to an invalid path!"
|
||
fi
|
||
|
||
if [[ "${FS,,}" == "fuse"* ]]; then
|
||
info "Warning: the filesystem of $STORAGE is FUSE, this extra layer will negatively affect performance!"
|
||
fi
|
||
|
||
if [[ "${FS,,}" == "ecryptfs" ]] || [[ "${FS,,}" == "tmpfs" ]]; then
|
||
info "Warning: the filesystem of $STORAGE is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
||
fi
|
||
|
||
if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; then
|
||
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
||
fi
|
||
|
||
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
||
TMP="$STORAGE/tmp"
|
||
else
|
||
TMP="/tmp/dsm"
|
||
TMP_SPACE=2147483648
|
||
SPACE=$(df --output=avail -B 1 /tmp | tail -n 1)
|
||
SPACE_MB=$(formatBytes "$SPACE")
|
||
if (( TMP_SPACE > SPACE )); then
|
||
error "Not enough free space inside the container, have $SPACE_MB available but need at least 2 GB." && exit 93
|
||
fi
|
||
fi
|
||
|
||
rm -rf "$TMP" && mkdir -p "$TMP"
|
||
|
||
# Check free diskspace
|
||
ROOT_SPACE=536870912
|
||
SPACE=$(df --output=avail -B 1 / | tail -n 1)
|
||
SPACE_MB=$(formatBytes "$SPACE" "down")
|
||
(( ROOT_SPACE > SPACE )) && error "Not enough free space inside the container, have $SPACE_MB available but need at least 500 MB." && exit 96
|
||
|
||
MIN_SPACE=15032385536
|
||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||
SPACE_GB=$(formatBytes "$SPACE")
|
||
(( MIN_SPACE > SPACE )) && error "Not enough free space for installation in $STORAGE, have $SPACE_GB available but need at least 14 GB." && exit 94
|
||
|
||
# Check if output is to interactive TTY
|
||
if [ -t 1 ]; then
|
||
PROGRESS="--progress=bar:noscroll"
|
||
else
|
||
PROGRESS="--progress=dot:giga"
|
||
fi
|
||
|
||
if [[ "$URL" == "file://"* ]]; then
|
||
MSG="Copying DSM"
|
||
ERR="Failed to copy ${URL:7}"
|
||
info "Install: Copying installation image..."
|
||
else
|
||
MSG="Downloading DSM"
|
||
ERR="Failed to download $URL"
|
||
info "Install: Downloading $BASE.pat..."
|
||
fi
|
||
|
||
html "$MSG..."
|
||
|
||
PAT="/$BASE.pat"
|
||
rm -f "$PAT"
|
||
|
||
if [[ "$URL" == "file://"* ]]; then
|
||
|
||
if [ ! -f "${URL:7}" ]; then
|
||
error "File '${URL:7}' does not exist!" && exit 65
|
||
fi
|
||
|
||
cp "${URL:7}" "$PAT"
|
||
|
||
else
|
||
|
||
SIZE=0
|
||
[[ "${URL,,}" == *"_72806.pat" ]] && SIZE=361010261
|
||
[[ "${URL,,}" == *"_69057.pat" ]] && SIZE=363837333
|
||
[[ "${URL,,}" == *"_42218.pat" ]] && SIZE=379637760
|
||
|
||
# /run/progress.sh "$PAT" "$SIZE" "$MSG ([P])..." &
|
||
|
||
{ wget "$URL" -O "$PAT" -q --no-check-certificate --timeout=10 --no-http-keep-alive --show-progress "$PROGRESS"; rc=$?; } || :
|
||
|
||
# fKill "progress.sh"
|
||
|
||
(( rc == 3 )) && error "$ERR , cannot write file (disk full?)" && exit 69
|
||
(( rc == 4 )) && error "$ERR , network failure!" && exit 69
|
||
(( rc == 8 )) && error "$ERR , server issued an error response!" && exit 69
|
||
(( rc != 0 )) && error "$ERR , reason: $rc" && exit 69
|
||
|
||
fi
|
||
|
||
[ ! -s "$PAT" ] && error "$ERR" && exit 69
|
||
|
||
SIZE=$(stat -c%s "$PAT")
|
||
|
||
if ((SIZE<250000000)); then
|
||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
||
fi
|
||
|
||
MSG="Extracting installation image..."
|
||
info "Install: $MSG" && html "$MSG"
|
||
|
||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||
|
||
tar xpf "$PAT" -C "$TMP/."
|
||
|
||
else
|
||
pip install dissect.cstruct msgpack pysodium
|
||
wget https://raw.githubusercontent.com/sud0woodo/patology/refs/heads/main/patology.py -O $TMP/extract.py
|
||
{ (cd "$TMP" && python3 extract.py -i "$PAT" -d 2>extract.log); rc=$?; } || :
|
||
|
||
if (( rc != 0 )); then
|
||
cat $TMP/extract.log
|
||
error "Failed to extract PAT file, reason $rc" && exit 63
|
||
fi
|
||
|
||
fi
|
||
|
||
MSG="Preparing system partition..."
|
||
info "Install: $MSG" && html "$MSG"
|
||
|
||
BOOT=$(find "$TMP" -name "*.bin.zip")
|
||
[ ! -s "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||
|
||
BOOT=$(echo "$BOOT" | head -c -5)
|
||
unzip -q -o "$BOOT".zip -d "$TMP"
|
||
|
||
SYSTEM="$STORAGE/$BASE.system.img"
|
||
rm -f "$SYSTEM"
|
||
|
||
# Check free diskspace
|
||
SYSTEM_SIZE=10738466816
|
||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
||
SPACE_MB=$(formatBytes "$SPACE")
|
||
|
||
if (( SYSTEM_SIZE > SPACE )); then
|
||
error "Not enough free space in $STORAGE to create a 10 GB system disk, have only $SPACE_MB available." && exit 97
|
||
fi
|
||
|
||
if ! touch "$SYSTEM"; then
|
||
error "Could not create file $SYSTEM for the system disk." && exit 98
|
||
fi
|
||
|
||
if [[ "${FS,,}" == "btrfs" ]]; then
|
||
{ chattr +C "$SYSTEM"; } || :
|
||
FA=$(lsattr "$SYSTEM")
|
||
if [[ "$FA" != *"C"* ]]; then
|
||
error "Failed to disable COW for system image $SYSTEM on ${FS^^} filesystem."
|
||
fi
|
||
fi
|
||
|
||
if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
||
if ! fallocate -l -x "$SYSTEM_SIZE" "$SYSTEM"; then
|
||
if ! truncate -s "$SYSTEM_SIZE" "$SYSTEM"; then
|
||
rm -f "$SYSTEM"
|
||
error "Could not allocate file $SYSTEM for the system disk." && exit 98
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
PART="$TMP/partition.fdisk"
|
||
|
||
{ echo "label: dos"
|
||
echo "label-id: 0x6f9ee2e9"
|
||
echo "device: $SYSTEM"
|
||
echo "unit: sectors"
|
||
echo "sector-size: 512"
|
||
echo ""
|
||
echo "${SYSTEM}1 : start= 2048, size= 16777216, type=83"
|
||
echo "${SYSTEM}2 : start= 16779264, size= 4194304, type=82"
|
||
} > "$PART"
|
||
|
||
sfdisk -q "$SYSTEM" < "$PART"
|
||
|
||
MOUNT="$TMP/system"
|
||
rm -rf "$MOUNT" && mkdir -p "$MOUNT"
|
||
|
||
MSG="Extracting system partition..."
|
||
info "Install: $MSG" && html "$MSG"
|
||
|
||
HDA="$TMP/hda1"
|
||
IDB="$TMP/indexdb"
|
||
PKG="$TMP/packages"
|
||
HDP="$TMP/synohdpack_img"
|
||
|
||
[ ! -s "$HDA.tgz" ] && error "The PAT file contains no OS image." && exit 64
|
||
mv "$HDA.tgz" "$HDA.txz"
|
||
|
||
[ -d "$PKG" ] && mv "$PKG/" "$MOUNT/.SynoUpgradePackages/"
|
||
rm -f "$MOUNT/.SynoUpgradePackages/ActiveInsight-"*
|
||
|
||
if [ -s "$IDB.txz" ]; then
|
||
INDEX_DB="$MOUNT/usr/syno/synoman/indexdb"
|
||
mkdir -p "$INDEX_DB"
|
||
fi
|
||
|
||
LABEL="1.44.1-42218"
|
||
OFFSET="1048576" # 2048 * 512
|
||
NUMBLOCKS="2097152" # (16777216 * 512) / 4096
|
||
MSG="Installing system partition..."
|
||
|
||
fakeroot -- bash -c "set -Eeu;\
|
||
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
||
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
||
tar xpfJ $HDA.txz --absolute-names --skip-old-files -C $MOUNT/;\
|
||
printf '%b%s%b' '\E[1;34m❯ \E[1;36m' 'Install: $MSG' '\E[0m\n';\
|
||
mke2fs -q -t ext4 -b 4096 -d $MOUNT/ -L $LABEL -F -E offset=$OFFSET $SYSTEM $NUMBLOCKS"
|
||
|
||
rm -rf "$MOUNT"
|
||
echo "$BASE" > "$STORAGE/dsm.ver"
|
||
|
||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||
rm -f "$PAT"
|
||
else
|
||
mv -f "$PAT" "$STORAGE/$BASE.pat"
|
||
fi
|
||
|
||
mv -f "$BOOT" "$STORAGE/$BASE.boot.img"
|
||
rm -rf "$TMP"
|
||
|
||
html "Booting DSM instance..."
|
||
sleep 1.2
|
||
|
||
return 0 |