Fred808 commited on
Commit
021894c
·
verified ·
1 Parent(s): f13dd78

Upload 33 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .dockerignore
2
+ .devcontainer
3
+ .git
4
+ .github
5
+ .gitignore
6
+ .gitlab-ci.yml
7
+ .gitmodules
8
+ Dockerfile
9
+ Dockerfile.archive
10
+ compose.yml
11
+ compose.yaml
12
+ docker-compose.yml
13
+ docker-compose.yaml
14
+
15
+ *.md
16
+
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+
Dockerfile ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM debian:trixie-slim
4
+
5
+ ARG TARGETARCH
6
+ ARG VERSION_ARG="0.0"
7
+ ARG VERSION_VNC="1.6.0"
8
+ ARG VERSION_UTK="1.2.0"
9
+ ARG VERSION_PASST="2025_09_19"
10
+
11
+ ARG DEBCONF_NOWARNINGS="yes"
12
+ ARG DEBIAN_FRONTEND="noninteractive"
13
+ ARG DEBCONF_NONINTERACTIVE_SEEN="true"
14
+
15
+ RUN set -eu && \
16
+ apt-get update && \
17
+ apt-get --no-install-recommends -y install \
18
+ bc \
19
+ jq \
20
+ xxd \
21
+ tini \
22
+ wget \
23
+ 7zip \
24
+ curl \
25
+ ovmf \
26
+ fdisk \
27
+ nginx \
28
+ swtpm \
29
+ procps \
30
+ ethtool \
31
+ iptables \
32
+ iproute2 \
33
+ dnsmasq \
34
+ xz-utils \
35
+ apt-utils \
36
+ net-tools \
37
+ e2fsprogs \
38
+ qemu-utils \
39
+ websocketd \
40
+ iputils-ping \
41
+ genisoimage \
42
+ inotify-tools \
43
+ netcat-openbsd \
44
+ ca-certificates \
45
+ qemu-system-x86 && \
46
+ wget "https://github.com/qemus/passt/releases/download/v${VERSION_PASST}/passt_${VERSION_PASST}_${TARGETARCH}.deb" -O /tmp/passt.deb -q && \
47
+ dpkg -i /tmp/passt.deb && \
48
+ apt-get clean && \
49
+ mkdir -p /etc/qemu && \
50
+ echo "allow br0" > /etc/qemu/bridge.conf && \
51
+ mkdir -p /usr/share/novnc && \
52
+ wget "https://github.com/novnc/noVNC/archive/refs/heads/master.tar.gz" -O /tmp/novnc.tar.gz -q --timeout=10 && \
53
+ tar -xf /tmp/novnc.tar.gz -C /tmp/ && \
54
+ cd "/tmp/noVNC-master" && \
55
+ mv app core vendor package.json ./*.html /usr/share/novnc && \
56
+ unlink /etc/nginx/sites-enabled/default && \
57
+ sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \
58
+ echo "$VERSION_ARG" > /run/version && \
59
+ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
60
+
61
+ COPY --chmod=755 ./src /run/
62
+ COPY --chmod=755 ./web /var/www/
63
+ COPY --chmod=664 ./web/conf/defaults.json /usr/share/novnc
64
+ COPY --chmod=664 ./web/conf/mandatory.json /usr/share/novnc
65
+ COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
66
+
67
+ ADD --chmod=755 "https://github.com/qemus/fiano/releases/download/v${VERSION_UTK}/utk_${VERSION_UTK}_${TARGETARCH}.bin" /run/utk.bin
68
+
69
+ VOLUME /storage
70
+ EXPOSE 22 5900 8006
71
+
72
+ ENV BOOT="alpine"
73
+ ENV CPU_CORES="2"
74
+ ENV RAM_SIZE="2G"
75
+ ENV DISK_SIZE="64G"
76
+
77
+ ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
compose.yml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ qemu:
3
+ image: qemux/qemu
4
+ container_name: qemu
5
+ environment:
6
+ BOOT: "mint"
7
+ devices:
8
+ - /dev/kvm
9
+ - /dev/net/tun
10
+ cap_add:
11
+ - NET_ADMIN
12
+ ports:
13
+ - 8006:8006
14
+ volumes:
15
+ - ./qemu:/storage
16
+ restart: always
17
+ stop_grace_period: 2m
kubernetes.yml ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ apiVersion: v1
3
+ kind: PersistentVolumeClaim
4
+ metadata:
5
+ name: qemu-pvc
6
+ spec:
7
+ accessModes:
8
+ - ReadWriteOnce
9
+ resources:
10
+ requests:
11
+ storage: 64Gi
12
+ ---
13
+ apiVersion: apps/v1
14
+ kind: Deployment
15
+ metadata:
16
+ name: qemu
17
+ labels:
18
+ name: qemu
19
+ spec:
20
+ replicas: 1
21
+ selector:
22
+ matchLabels:
23
+ app: qemu
24
+ template:
25
+ metadata:
26
+ labels:
27
+ app: qemu
28
+ spec:
29
+ containers:
30
+ - name: qemu
31
+ image: qemux/qemu
32
+ env:
33
+ - name: BOOT
34
+ value: "mint"
35
+ - name: DISK_SIZE
36
+ value: "64G"
37
+ ports:
38
+ - containerPort: 8006
39
+ name: http
40
+ protocol: TCP
41
+ - containerPort: 5900
42
+ name: vnc
43
+ protocol: TCP
44
+ securityContext:
45
+ capabilities:
46
+ add:
47
+ - NET_ADMIN
48
+ privileged: true
49
+ volumeMounts:
50
+ - mountPath: /storage
51
+ name: storage
52
+ - mountPath: /dev/kvm
53
+ name: dev-kvm
54
+ - mountPath: /dev/net/tun
55
+ name: dev-tun
56
+ terminationGracePeriodSeconds: 120
57
+ volumes:
58
+ - name: storage
59
+ persistentVolumeClaim:
60
+ claimName: qemu-pvc
61
+ - hostPath:
62
+ path: /dev/kvm
63
+ name: dev-kvm
64
+ - hostPath:
65
+ path: /dev/net/tun
66
+ type: CharDevice
67
+ name: dev-tun
68
+ ---
69
+ apiVersion: v1
70
+ kind: Service
71
+ metadata:
72
+ name: qemu
73
+ spec:
74
+ internalTrafficPolicy: Cluster
75
+ ports:
76
+ - name: http
77
+ port: 8006
78
+ protocol: TCP
79
+ targetPort: 8006
80
+ - name: vnc
81
+ port: 5900
82
+ protocol: TCP
83
+ targetPort: 5900
84
+ selector:
85
+ app: qemu
86
+ type: ClusterIP
license.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
src/boot.sh ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+ : "${BIOS:=""}" # BIOS file
6
+ : "${TPM:="N"}" # Disable TPM
7
+ : "${SMM:="N"}" # Disable SMM
8
+
9
+ BOOT_DESC=""
10
+ BOOT_OPTS=""
11
+
12
+ SECURE="off"
13
+ [[ "$SMM" == [Yy1]* ]] && SECURE="on"
14
+ [ -n "$BIOS" ] && BOOT_MODE="custom"
15
+
16
+ msg="Configuring boot..."
17
+ html "$msg"
18
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
19
+
20
+ case "${BOOT_MODE,,}" in
21
+ "uefi" | "" )
22
+ BOOT_MODE="uefi"
23
+ ROM="OVMF_CODE_4M.fd"
24
+ VARS="OVMF_VARS_4M.fd"
25
+ ;;
26
+ "secure" )
27
+ SECURE="on"
28
+ BOOT_DESC=" securely"
29
+ ROM="OVMF_CODE_4M.secboot.fd"
30
+ VARS="OVMF_VARS_4M.secboot.fd"
31
+ ;;
32
+ "windows" | "windows_plain" )
33
+ ROM="OVMF_CODE_4M.fd"
34
+ VARS="OVMF_VARS_4M.fd"
35
+ ;;
36
+ "windows_secure" )
37
+ TPM="Y"
38
+ SECURE="on"
39
+ BOOT_DESC=" securely"
40
+ ROM="OVMF_CODE_4M.ms.fd"
41
+ VARS="OVMF_VARS_4M.ms.fd"
42
+ ;;
43
+ "windows_legacy" )
44
+ HV="N"
45
+ SECURE="on"
46
+ BOOT_DESC=" (legacy)"
47
+ [ -z "${USB:-}" ] && USB="usb-ehci,id=ehci"
48
+ ;;
49
+ "legacy" )
50
+ BOOT_DESC=" with SeaBIOS"
51
+ ;;
52
+ "custom" )
53
+ BOOT_OPTS="-bios $BIOS"
54
+ BOOT_DESC=" with custom BIOS file"
55
+ ;;
56
+ *)
57
+ error "Unknown BOOT_MODE, value \"${BOOT_MODE}\" is not recognized!"
58
+ exit 33
59
+ ;;
60
+ esac
61
+
62
+ if [[ "${BOOT_MODE,,}" == "windows"* ]]; then
63
+ BOOT_OPTS+=" -rtc base=localtime"
64
+ BOOT_OPTS+=" -global ICH9-LPC.disable_s3=1"
65
+ BOOT_OPTS+=" -global ICH9-LPC.disable_s4=1"
66
+ fi
67
+
68
+ case "${BOOT_MODE,,}" in
69
+ "uefi" | "secure" | "windows" | "windows_plain" | "windows_secure" )
70
+
71
+ OVMF="/usr/share/OVMF"
72
+ DEST="$STORAGE/${BOOT_MODE,,}"
73
+
74
+ if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then
75
+ [ ! -s "$OVMF/$ROM" ] || [ ! -f "$OVMF/$ROM" ] && error "UEFI boot file ($OVMF/$ROM) not found!" && exit 44
76
+ if [[ "${LOGO:-}" == [Nn]* ]]; then
77
+ cp "$OVMF/$ROM" "$DEST.tmp"
78
+ else
79
+ /run/utk.bin "$OVMF/$ROM" replace_ffs LogoDXE "/var/www/img/${PROCESS,,}.ffs" save "$DEST.tmp"
80
+ fi
81
+ mv "$DEST.tmp" "$DEST.rom"
82
+ ! setOwner "$DEST.rom" && error "Failed to set the owner for \"$DEST.rom\" !"
83
+ fi
84
+
85
+ if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then
86
+ [ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45
87
+ cp "$OVMF/$VARS" "$DEST.tmp"
88
+ mv "$DEST.tmp" "$DEST.vars"
89
+ ! setOwner "$DEST.vars" && error "Failed to set the owner for \"$DEST.vars\" !"
90
+ fi
91
+
92
+ if [[ "${BOOT_MODE,,}" == "secure" || "${BOOT_MODE,,}" == "windows_secure" ]]; then
93
+ BOOT_OPTS+=" -global driver=cfi.pflash01,property=secure,value=on"
94
+ fi
95
+
96
+ BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on"
97
+ BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw"
98
+
99
+ ;;
100
+ esac
101
+
102
+ MSRS="/sys/module/kvm/parameters/ignore_msrs"
103
+ if [ -e "$MSRS" ]; then
104
+ result=$(<"$MSRS")
105
+ result="${result//[![:print:]]/}"
106
+ if [[ "$result" == "0" || "${result^^}" == "N" ]]; then
107
+ echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
108
+ fi
109
+ fi
110
+
111
+ CLOCKSOURCE="tsc"
112
+ [[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
113
+ CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
114
+
115
+ if [ ! -f "$CLOCK" ]; then
116
+ warn "file \"$CLOCK\" cannot not found?"
117
+ else
118
+ result=$(<"$CLOCK")
119
+ result="${result//[![:print:]]/}"
120
+ case "${result,,}" in
121
+ "${CLOCKSOURCE,,}" ) ;;
122
+ "kvm-clock" ) info "Nested KVM virtualization detected.." ;;
123
+ "hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
124
+ "hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
125
+ *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
126
+ esac
127
+ fi
128
+
129
+ SM_BIOS=""
130
+ PS="/sys/class/dmi/id/product_serial"
131
+
132
+ if [ -s "$PS" ] && [ -r "$PS" ]; then
133
+
134
+ BIOS_SERIAL=$(<"$PS")
135
+ BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}"
136
+
137
+ if [ -n "$BIOS_SERIAL" ]; then
138
+ SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL"
139
+ fi
140
+
141
+ fi
142
+
143
+ rm -f /var/run/tpm.pid
144
+
145
+ if [[ "$TPM" == [Yy1]* ]]; then
146
+
147
+ if ! swtpm socket -t -d --tpmstate "backend-uri=file://$STORAGE/${BOOT_MODE,,}.tpm" --ctrl type=unixio,path=/run/swtpm-sock --pid file=/var/run/tpm.pid --tpm2; then
148
+ error "Failed to start TPM emulator, reason: $?"
149
+ else
150
+
151
+ for (( i = 1; i < 20; i++ )); do
152
+
153
+ [ -S "/run/swtpm-sock" ] && break
154
+
155
+ if (( i % 10 == 0 )); then
156
+ echo "Waiting for TPM emulator to become available..."
157
+ fi
158
+
159
+ sleep 0.1
160
+
161
+ done
162
+
163
+ if [ ! -S "/run/swtpm-sock" ]; then
164
+ error "TPM socket not found? Disabling TPM module..."
165
+ else
166
+ BOOT_OPTS+=" -chardev socket,id=chrtpm,path=/run/swtpm-sock"
167
+ BOOT_OPTS+=" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
168
+ fi
169
+
170
+ fi
171
+ fi
172
+
173
+ return 0
src/config.sh ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ : "${UUID:=""}"
5
+ : "${HPET:="off"}"
6
+ : "${VMPORT:="off"}"
7
+ : "${SERIAL:="mon:stdio"}"
8
+ : "${USB:="qemu-xhci,id=xhci,p2=7,p3=7"}"
9
+ : "${MONITOR:="telnet:localhost:$MON_PORT,server,nowait,nodelay"}"
10
+ : "${SMP:="$CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"}"
11
+
12
+ msg="Configuring QEMU..."
13
+ html "$msg"
14
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
15
+
16
+ DEV_OPTS=""
17
+ DEF_OPTS="-nodefaults"
18
+ SERIAL_OPTS="-serial $SERIAL"
19
+ CPU_OPTS="-cpu $CPU_FLAGS -smp $SMP"
20
+ RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
21
+ MON_OPTS="-monitor $MONITOR -name $PROCESS,process=$PROCESS,debug-threads=on"
22
+ MAC_OPTS="-machine type=${MACHINE},smm=${SECURE},graphics=off,vmport=${VMPORT},dump-guest-core=off,hpet=${HPET}${KVM_OPTS}"
23
+
24
+ [ -n "$UUID" ] && MAC_OPTS+=" -uuid $UUID"
25
+ [ -n "$SM_BIOS" ] && MAC_OPTS+=" $SM_BIOS"
26
+
27
+ if [[ "${MACHINE,,}" != "pc"* ]]; then
28
+ DEV_OPTS="-object rng-random,id=objrng0,filename=/dev/urandom"
29
+ DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0"
30
+ if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
31
+ DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=pcie.0"
32
+ fi
33
+ fi
34
+
35
+ if [ -d "/shared" ] && [[ "${BOOT_MODE,,}" != "windows"* ]]; then
36
+ DEV_OPTS+=" -fsdev local,id=fsdev0,path=/shared,security_model=none"
37
+ DEV_OPTS+=" -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=shared"
38
+ fi
39
+
40
+ [ -n "$USB" ] && [[ "${USB,,}" != "no"* ]] && USB_OPTS="-device $USB -device usb-tablet"
41
+
42
+ ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS ${USB_OPTS:-} $NET_OPTS $DISK_OPTS $BOOT_OPTS $DEV_OPTS $ARGUMENTS"
43
+ ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
44
+
45
+ return 0
src/define.sh ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ pipe() {
5
+ local code="99"
6
+ msg="Failed to connect to $1, reason:"
7
+
8
+ curl --disable --silent --max-time 15 --fail --location "${1}" || {
9
+ code="$?"
10
+ }
11
+
12
+ case "${code,,}" in
13
+ "6" ) error "$msg could not resolve host!" ;;
14
+ "7" ) error "$msg no internet connection available!" ;;
15
+ "28" ) error "$msg connection timed out!" ;;
16
+ "99" ) return 0 ;;
17
+ *) error "$msg $code" ;;
18
+ esac
19
+
20
+ return 1
21
+ }
22
+
23
+ getURL() {
24
+ local id="${1/ /}"
25
+ local ret="$2"
26
+ local url=""
27
+ local arm=""
28
+ local name=""
29
+ local body=""
30
+ local version=""
31
+
32
+ case "${id,,}" in
33
+ "alma" | "almalinux" | "alma-linux" )
34
+ version="9"
35
+ name="AlmaLinux"
36
+ if [[ "$ret" == "url" ]]; then
37
+ url="https://repo.almalinux.org/almalinux/${version}/live/x86_64/AlmaLinux-${version}-latest-x86_64-Live-GNOME.iso"
38
+ arm="https://repo.almalinux.org/almalinux/${version}/live/aarch64/AlmaLinux-${version}-latest-aarch64-Live-GNOME.iso"
39
+ fi ;;
40
+ "alpine" | "alpinelinux" | "alpine-linux" )
41
+ name="Alpine Linux"
42
+ if [[ "$ret" == "url" ]]; then
43
+ body=$(pipe "https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/latest-releases.yaml") || exit 65
44
+ version=$(echo "$body" | awk '/"Xen"/{found=0} {if(found) print} /"Virtual"/{found=1}' | grep 'version:' | awk '{print $2}')
45
+ url="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-virt-$version-x86_64.iso"
46
+ arm="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/aarch64/alpine-virt-$version-aarch64.iso"
47
+ fi ;;
48
+ "arch" | "archlinux" | "arch-linux" )
49
+ name="Arch Linux"
50
+ if [[ "$ret" == "url" ]]; then
51
+ url="https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso"
52
+ fi ;;
53
+ "cachy" | "cachyos" )
54
+ name="CachyOS"
55
+ if [[ "$ret" == "url" ]]; then
56
+ body=$(pipe "https://cachyos.org/download/") || exit 65
57
+ url=$(echo "$body" | tr '&' '\n' | grep "ISO/desktop" | grep -v 'iso.sha' | grep -v 'iso.sig' | cut -d';' -f2)
58
+ arm=$(echo "$body" | tr '&' '\n' | grep "ISO/handheld" | grep -v 'iso.sha' | grep -v 'iso.sig' | cut -d';' -f2)
59
+ fi ;;
60
+ "centos" | "centosstream" | "centos-stream" )
61
+ name="CentOS Stream"
62
+ if [[ "$ret" == "url" ]]; then
63
+ body=$(pipe "https://linuxsoft.cern.ch/centos-stream/") || exit 65
64
+ version=$(echo "$body" | grep "\-stream" | cut -d'"' -f 6 | cut -d'-' -f 1 | head -n 1)
65
+ url="https://mirrors.xtom.de/centos-stream/$version-stream/BaseOS/x86_64/iso/CentOS-Stream-$version-latest-x86_64-dvd1.iso"
66
+ arm="https://mirrors.xtom.de/centos-stream/$version-stream/BaseOS/aarch64/iso/CentOS-Stream-$version-latest-aarch64-dvd1.iso"
67
+ fi ;;
68
+ "debian" )
69
+ name="Debian"
70
+ if [[ "$ret" == "url" ]]; then
71
+ body=$(pipe "https://cdimage.debian.org/debian-cd/") || exit 65
72
+ version=$(echo "$body" | grep '\.[0-9]/' | cut -d'>' -f 9 | cut -d'/' -f 1)
73
+ url="https://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid/debian-live-$version-amd64-standard.iso"
74
+ arm="https://cdimage.debian.org/debian-cd/current/arm64/iso-dvd/debian-$version-arm64-DVD-1.iso"
75
+ fi ;;
76
+ "fedora" | "fedoralinux" | "fedora-linux" )
77
+ name="Fedora Linux"
78
+ if [[ "$ret" == "url" ]]; then
79
+ body=$(pipe "https://getfedora.org/releases.json") || exit 65
80
+ version=$(echo "$body" | jq -r 'map(.version) | unique | .[]' | sed 's/ /_/g' | sed '/_Beta/d' | sort -r | head -n 1)
81
+ url=$(echo "$body" | jq -r "map(select(.arch==\"x86_64\" and .version==\"${version}\" and .variant==\"Workstation\" and .subvariant==\"Workstation\" )) | .[].link" | grep -m 1 .iso)
82
+ arm=$(echo "$body" | jq -r "map(select(.arch==\"aarch64\" and .version==\"${version}\" and .variant==\"Workstation\" and .subvariant==\"Workstation\" )) | .[].link" | grep -m 1 .iso)
83
+ fi ;;
84
+ "gentoo" | "gentoolinux" | "gentoo-linux" )
85
+ name="Gentoo Linux"
86
+ if [[ "$ret" == "url" ]]; then
87
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
88
+ body=$(pipe "https://distfiles.gentoo.org/releases/amd64/autobuilds/latest-iso.txt") || exit 65
89
+ version=$(echo "$body" | grep livegui | cut -d' ' -f1)
90
+ url="https://distfiles.gentoo.org/releases/amd64/autobuilds/$version"
91
+ else
92
+ body=$(pipe "https://distfiles.gentoo.org/releases/arm64/autobuilds/latest-qcow2.txt") || exit 65
93
+ version=$(echo "$body" | grep cloudinit | cut -d' ' -f1)
94
+ arm="https://distfiles.gentoo.org/releases/arm64/autobuilds/$version"
95
+ fi
96
+ fi ;;
97
+ "kali" | "kalilinux" | "kali-linux" )
98
+ name="Kali Linux"
99
+ if [[ "$ret" == "url" ]]; then
100
+ body=$(pipe "https://cdimage.kali.org/current/?C=M;O=D") || exit 65
101
+ version=$(echo "$body" | grep -o ">kali-linux-.*-live-amd64.iso" | head -n 1 | cut -c 2-)
102
+ url="https://cdimage.kali.org/current/$version"
103
+ version=$(echo "$body" | grep -o ">kali-linux-.*-live-arm64.iso" | head -n 1 | cut -c 2-)
104
+ arm="https://cdimage.kali.org/current/$version"
105
+ fi ;;
106
+ "kubuntu" )
107
+ name="Kubuntu"
108
+ if [[ "$ret" == "url" ]]; then
109
+ body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65
110
+ version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version')
111
+ url="https://cdimage.ubuntu.com/kubuntu/releases/${version}/release/kubuntu-${version}-desktop-amd64.iso"
112
+ fi ;;
113
+ "lmde" )
114
+ version="6"
115
+ name="Linux Mint Debian Edition"
116
+ if [[ "$ret" == "url" ]]; then
117
+ url="https://pub.linuxmint.io/debian/lmde-${version}-cinnamon-64bit.iso"
118
+ fi ;;
119
+ "macos" | "osx" )
120
+ name="macOS"
121
+ error "To install $name use: https://github.com/dockur/macos" && return 1 ;;
122
+ "mint" | "linuxmint" | "linux-mint" )
123
+ version="22.2"
124
+ name="Linux Mint"
125
+ if [[ "$ret" == "url" ]]; then
126
+ url="https://pub.linuxmint.io/stable/${version}/linuxmint-${version}-cinnamon-64bit.iso"
127
+ fi ;;
128
+ "manjaro" )
129
+ name="Manjaro"
130
+ if [[ "$ret" == "url" ]]; then
131
+ body=$(pipe "https://gitlab.manjaro.org/web/iso-info/-/raw/master/file-info.json") || exit 65
132
+ url=$(echo "$body" | jq -r .official.plasma.image)
133
+ fi ;;
134
+ "mx" | "mxlinux" | "mx-linux" )
135
+ name="MX Linux"
136
+ if [[ "$ret" == "url" ]]; then
137
+ version=$(curl --disable -Ils "https://sourceforge.net/projects/mx-linux/files/latest/download" | grep -i 'location:' | cut -d? -f1 | cut -d_ -f1 | cut -d- -f3) || exit 65
138
+ url="https://mirror.umd.edu/mxlinux-iso/MX/Final/Xfce/MX-${version}_x64.iso"
139
+ fi ;;
140
+ "nixos" )
141
+ name="NixOS"
142
+ if [[ "$ret" == "url" ]]; then
143
+ body=$(pipe "https://nix-channels.s3.amazonaws.com/?delimiter=/") || exit 65
144
+ version=$(echo "$body" | grep -o -E 'nixos-[[:digit:]]+\.[[:digit:]]+' | cut -d- -f2 | sort -nru | head -n 1)
145
+ url="https://channels.nixos.org/nixos-$version/latest-nixos-gnome-x86_64-linux.iso"
146
+ arm="https://channels.nixos.org/nixos-$version/latest-nixos-gnome-aarch64-linux.iso"
147
+ fi ;;
148
+ "opensuse" | "open-suse" | "suse" )
149
+ name="OpenSUSE"
150
+ if [[ "$ret" == "url" ]]; then
151
+ body=$(pipe "https://download.opensuse.org/distribution/leap/") || exit 65
152
+ version=$(echo "$body" | grep 'class="name"' | cut -d '/' -f2 | grep -v 42 | sort -r | head -n 1)
153
+ url="https://download.opensuse.org/distribution/leap/$version/installer/iso/agama-installer.x86_64-Leap_${version}.iso"
154
+ arm="https://download.opensuse.org/distribution/leap/$version/installer/iso/agama-installer.aarch64-Leap_${version}.iso"
155
+ fi ;;
156
+ "rocky" | "rockylinux" | "rocky-linux" )
157
+ version="9"
158
+ name="Rocky Linux"
159
+ if [[ "$ret" == "url" ]]; then
160
+ url="https://dl.rockylinux.org/pub/rocky/${version}/live/x86_64/Rocky-${version}-Workstation-x86_64-latest.iso"
161
+ arm="https://dl.rockylinux.org/pub/rocky/${version}/live/aarch64/Rocky-${version}-Workstation-aarch64-latest.iso"
162
+ fi ;;
163
+ "slack" | "slackware" )
164
+ name="Slackware"
165
+ if [[ "$ret" == "url" ]]; then
166
+ url="https://slackware.nl/slackware-live/slackware64-current-live/slackware64-live-current.iso"
167
+ fi ;;
168
+ "tails" )
169
+ name="Tails"
170
+ if [[ "$ret" == "url" ]]; then
171
+ body=$(pipe "https://tails.net/install/v2/Tails/amd64/stable/latest.json") || exit 65
172
+ url=$(echo "$body" | jq -r '.installations[0]."installation-paths"[]|select(.type=="iso")|."target-files"[0].url')
173
+ fi ;;
174
+ "ubuntu" | "ubuntu-desktop" )
175
+ name="Ubuntu Desktop"
176
+ if [[ "$ret" == "url" ]]; then
177
+ body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65
178
+ version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version')
179
+ url="https://releases.ubuntu.com/${version}/ubuntu-${version}-desktop-amd64.iso"
180
+ arm="https://cdimage.ubuntu.com/releases/${version}/release/ubuntu-${version}-desktop-arm64.iso"
181
+ fi ;;
182
+ "ubuntus" | "ubuntu-server")
183
+ name="Ubuntu Server"
184
+ if [[ "$ret" == "url" ]]; then
185
+ body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65
186
+ version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version')
187
+ url="https://releases.ubuntu.com/${version}/ubuntu-${version}-live-server-amd64.iso"
188
+ arm="https://cdimage.ubuntu.com/releases/${version}/release/ubuntu-${version}-live-server-arm64.iso"
189
+ fi ;;
190
+ "windows" )
191
+ name="Windows"
192
+ error "To install $name use: https://github.com/dockur/windows" && return 1 ;;
193
+ "xubuntu" )
194
+ name="Xubuntu"
195
+ if [[ "$ret" == "url" ]]; then
196
+ body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65
197
+ version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version')
198
+ url="https://cdimages.ubuntu.com/xubuntu/releases/${version}/release/xubuntu-${version}-desktop-amd64.iso"
199
+ fi ;;
200
+ "zorin" | "zorinos" | "zorin-os" )
201
+ name="Zorin OS"
202
+ if [[ "$ret" == "url" ]]; then
203
+ url="https://mirrors.edge.kernel.org/zorinos-isos/18/Zorin-OS-18-Core-64-bit.iso"
204
+ fi ;;
205
+ esac
206
+
207
+ case "${ret,,}" in
208
+ "name" )
209
+ echo "$name"
210
+ ;;
211
+ "url" )
212
+
213
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
214
+ if [ -n "$name" ] && [ -z "$url" ]; then
215
+ error "No image for $name available!"
216
+ return 1
217
+ fi
218
+ else
219
+ if [ -n "$name" ] && [ -z "$arm" ]; then
220
+ error "No image for $name is available for ARM64 yet! "
221
+ return 1
222
+ fi
223
+ fi
224
+
225
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
226
+ echo "$url"
227
+ else
228
+ echo "$arm"
229
+ fi ;;
230
+ esac
231
+
232
+ return 0
233
+ }
234
+
235
+ return 0
src/disk.sh ADDED
@@ -0,0 +1,798 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+
6
+ : "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
7
+ : "${DISK_FMT:=""}" # Disk file format, can be set to "raw" (default) or "qcow2"
8
+ : "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
9
+ : "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
10
+ : "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
11
+ : "${DISK_DISCARD:="on"}" # Controls whether unmap (TRIM) commands are passed to the host.
12
+ : "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
13
+
14
+ fmt2ext() {
15
+ local DISK_FMT="$1"
16
+
17
+ case "${DISK_FMT,,}" in
18
+ qcow2)
19
+ echo "qcow2"
20
+ ;;
21
+ raw)
22
+ echo "img"
23
+ ;;
24
+ *)
25
+ error "Unrecognized disk format: $DISK_FMT" && exit 78
26
+ ;;
27
+ esac
28
+ }
29
+
30
+ ext2fmt() {
31
+ local DISK_EXT="$1"
32
+
33
+ case "${DISK_EXT,,}" in
34
+ qcow2)
35
+ echo "qcow2"
36
+ ;;
37
+ img)
38
+ echo "raw"
39
+ ;;
40
+ *)
41
+ error "Unrecognized file extension: .$DISK_EXT" && exit 78
42
+ ;;
43
+ esac
44
+ }
45
+
46
+ getSize() {
47
+ local DISK_FILE="$1"
48
+ local DISK_EXT DISK_FMT
49
+
50
+ DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
51
+ DISK_FMT=$(ext2fmt "$DISK_EXT")
52
+
53
+ case "${DISK_FMT,,}" in
54
+ raw)
55
+ stat -c%s "$DISK_FILE"
56
+ ;;
57
+ qcow2)
58
+ qemu-img info "$DISK_FILE" -f "$DISK_FMT" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/'
59
+ ;;
60
+ *)
61
+ error "Unrecognized disk format: $DISK_FMT" && exit 78
62
+ ;;
63
+ esac
64
+ }
65
+
66
+ isCow() {
67
+ local FS="$1"
68
+
69
+ if [[ "${FS,,}" == "btrfs" ]]; then
70
+ return 0
71
+ fi
72
+
73
+ return 1
74
+ }
75
+
76
+ supportsDirect() {
77
+ local FS="$1"
78
+
79
+ if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
80
+ return 1
81
+ fi
82
+
83
+ return 0
84
+ }
85
+
86
+ createDisk() {
87
+
88
+ local DISK_FILE="$1"
89
+ local DISK_SPACE="$2"
90
+ local DISK_DESC="$3"
91
+ local DISK_FMT="$4"
92
+ local FS="$5"
93
+ local DATA_SIZE DIR SPACE GB FA
94
+
95
+ rm -f "$DISK_FILE"
96
+
97
+ DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
98
+
99
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
100
+
101
+ # Check free diskspace
102
+ DIR=$(dirname "$DISK_FILE")
103
+ SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
104
+
105
+ if (( DATA_SIZE > SPACE )); then
106
+ GB=$(formatBytes "$SPACE")
107
+ error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available..."
108
+ error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
109
+ fi
110
+
111
+ fi
112
+
113
+ html "Creating a $DISK_DESC image..."
114
+ info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
115
+
116
+ local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
117
+
118
+ case "${DISK_FMT,,}" in
119
+ raw)
120
+
121
+ if isCow "$FS"; then
122
+ if ! touch "$DISK_FILE"; then
123
+ error "$FAIL" && exit 77
124
+ fi
125
+ { chattr +C "$DISK_FILE"; } || :
126
+ fi
127
+
128
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
129
+
130
+ # Create an empty file
131
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
132
+ rm -f "$DISK_FILE"
133
+ error "$FAIL" && exit 77
134
+ fi
135
+
136
+ else
137
+
138
+ # Create an empty file
139
+ if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
140
+ if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
141
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
142
+ rm -f "$DISK_FILE"
143
+ error "$FAIL" && exit 77
144
+ fi
145
+ fi
146
+ fi
147
+
148
+ fi
149
+ ;;
150
+ qcow2)
151
+
152
+ local DISK_PARAM="$DISK_ALLOC"
153
+ isCow "$FS" && DISK_PARAM+=",nocow=on"
154
+ [ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
155
+
156
+ if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
157
+ rm -f "$DISK_FILE"
158
+ error "$FAIL" && exit 70
159
+ fi
160
+ ;;
161
+ esac
162
+
163
+ if isCow "$FS"; then
164
+ FA=$(lsattr "$DISK_FILE")
165
+ if [[ "$FA" != *"C"* ]]; then
166
+ error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
167
+ fi
168
+ fi
169
+
170
+ return 0
171
+ }
172
+
173
+ resizeDisk() {
174
+
175
+ local DISK_FILE="$1"
176
+ local DISK_SPACE="$2"
177
+ local DISK_DESC="$3"
178
+ local DISK_FMT="$4"
179
+ local FS="$5"
180
+ local CUR_SIZE DATA_SIZE DIR SPACE GB
181
+
182
+ CUR_SIZE=$(getSize "$DISK_FILE")
183
+ DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
184
+ local REQ=$(( DATA_SIZE - CUR_SIZE ))
185
+ (( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
186
+
187
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
188
+
189
+ # Check free diskspace
190
+ DIR=$(dirname "$DISK_FILE")
191
+ SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
192
+
193
+ if (( REQ > SPACE )); then
194
+ GB=$(formatBytes "$SPACE")
195
+ error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.."
196
+ error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
197
+ fi
198
+
199
+ fi
200
+
201
+ GB=$(formatBytes "$CUR_SIZE")
202
+ MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/G/ GB}..."
203
+ info "$MSG" && html "$MSG"
204
+
205
+ local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB} to ${DISK_SPACE/G/ GB} ($DISK_FILE)"
206
+
207
+ case "${DISK_FMT,,}" in
208
+ raw)
209
+
210
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
211
+
212
+ # Resize file by changing its length
213
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
214
+ error "$FAIL" && exit 75
215
+ fi
216
+
217
+ else
218
+
219
+ # Resize file by allocating more space
220
+ if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
221
+ if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
222
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
223
+ error "$FAIL" && exit 75
224
+ fi
225
+ fi
226
+ fi
227
+
228
+ fi
229
+ ;;
230
+ qcow2)
231
+
232
+ if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
233
+ error "$FAIL" && exit 72
234
+ fi
235
+
236
+ ;;
237
+ esac
238
+
239
+ return 0
240
+ }
241
+
242
+ convertDisk() {
243
+
244
+ local SOURCE_FILE="$1"
245
+ local SOURCE_FMT="$2"
246
+ local DST_FILE="$3"
247
+ local DST_FMT="$4"
248
+ local DISK_BASE="$5"
249
+ local DISK_DESC="$6"
250
+ local FS="$7"
251
+
252
+ [ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
253
+ [ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79
254
+
255
+ local TMP_FILE="$DISK_BASE.tmp"
256
+ rm -f "$TMP_FILE"
257
+
258
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
259
+
260
+ local DIR CUR_SIZE SPACE GB
261
+
262
+ # Check free diskspace
263
+ DIR=$(dirname "$TMP_FILE")
264
+ CUR_SIZE=$(getSize "$SOURCE_FILE")
265
+ SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
266
+
267
+ if (( CUR_SIZE > SPACE )); then
268
+ GB=$(formatBytes "$SPACE")
269
+ error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
270
+ error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
271
+ fi
272
+
273
+ fi
274
+
275
+ local msg="Converting $DISK_DESC to $DST_FMT"
276
+ html "$msg..."
277
+ info "$msg, please wait until completed..."
278
+
279
+ local CONV_FLAGS="-p"
280
+ local DISK_PARAM="$DISK_ALLOC"
281
+ isCow "$FS" && DISK_PARAM+=",nocow=on"
282
+
283
+ if [[ "$DST_FMT" != "raw" ]]; then
284
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
285
+ CONV_FLAGS+=" -c"
286
+ fi
287
+ [ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
288
+ fi
289
+
290
+ # shellcheck disable=SC2086
291
+ if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
292
+ rm -f "$TMP_FILE"
293
+ error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79
294
+ fi
295
+
296
+ if [[ "$DST_FMT" == "raw" ]]; then
297
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
298
+ # Work around qemu-img bug
299
+ CUR_SIZE=$(stat -c%s "$TMP_FILE")
300
+ if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
301
+ if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; then
302
+ error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
303
+ fi
304
+ fi
305
+ fi
306
+ fi
307
+
308
+ rm -f "$SOURCE_FILE"
309
+ mv "$TMP_FILE" "$DST_FILE"
310
+
311
+ if isCow "$FS"; then
312
+ FA=$(lsattr "$DST_FILE")
313
+ if [[ "$FA" != *"C"* ]]; then
314
+ error "Failed to disable COW for $DISK_DESC image $DST_FILE on ${FS^^} filesystem (returned $FA)"
315
+ fi
316
+ fi
317
+
318
+ msg="Conversion of $DISK_DESC"
319
+ html "$msg completed..."
320
+ info "$msg to $DST_FMT completed successfully!"
321
+
322
+ return 0
323
+ }
324
+
325
+ checkFS () {
326
+
327
+ local FS="$1"
328
+ local DISK_FILE="$2"
329
+ local DISK_DESC="$3"
330
+ local DIR FA
331
+
332
+ DIR=$(dirname "$DISK_FILE")
333
+ [ ! -d "$DIR" ] && return 0
334
+
335
+ if [[ "${FS,,}" == "overlay"* && "$PODMAN" != [Yy1]* ]]; then
336
+ warn "the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
337
+ fi
338
+
339
+ if [[ "${FS,,}" == "fuse"* ]]; then
340
+ warn "the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
341
+ fi
342
+
343
+ if ! supportsDirect "$FS"; then
344
+ warn "the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
345
+ fi
346
+
347
+ if isCow "$FS"; then
348
+ if [ -f "$DISK_FILE" ]; then
349
+ FA=$(lsattr "$DISK_FILE")
350
+ if [[ "$FA" != *"C"* ]]; then
351
+ warn "COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
352
+ fi
353
+ fi
354
+ fi
355
+
356
+ return 0
357
+ }
358
+
359
+ createDevice () {
360
+
361
+ local DISK_FILE="$1"
362
+ local DISK_TYPE="$2"
363
+ local DISK_INDEX="$3"
364
+ local DISK_ADDRESS="$4"
365
+ local DISK_FMT="$5"
366
+ local DISK_IO="$6"
367
+ local DISK_CACHE="$7"
368
+ local DISK_SERIAL="$8"
369
+ local DISK_SECTORS="$9"
370
+ local DISK_ID="data$DISK_INDEX"
371
+
372
+ local index=""
373
+ [ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
374
+ local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
375
+
376
+ case "${DISK_TYPE,,}" in
377
+ "none" ) ;;
378
+ "auto" )
379
+ echo "$result"
380
+ ;;
381
+ "usb" )
382
+ result+=",if=none \
383
+ -device usb-storage,drive=${DISK_ID}${index}${DISK_SERIAL}${DISK_SECTORS}"
384
+ echo "$result"
385
+ ;;
386
+ "nvme" )
387
+ result+=",if=none \
388
+ -device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}${DISK_SERIAL}${DISK_SECTORS}"
389
+ echo "$result"
390
+ ;;
391
+ "ide" | "sata" )
392
+ result+=",if=none \
393
+ -device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
394
+ -device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
395
+ echo "$result"
396
+ ;;
397
+ "blk" | "virtio-blk" )
398
+ result+=",if=none \
399
+ -device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}${DISK_SERIAL}${DISK_SECTORS}"
400
+ echo "$result"
401
+ ;;
402
+ "scsi" | "virtio-scsi" )
403
+ result+=",if=none \
404
+ -device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
405
+ -device scsi-hd,drive=${DISK_ID},bus=${DISK_ID}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
406
+ echo "$result"
407
+ ;;
408
+ esac
409
+
410
+ return 0
411
+ }
412
+
413
+ addMedia () {
414
+
415
+ local DISK_FILE="$1"
416
+ local DISK_TYPE="$2"
417
+ local DISK_INDEX="$3"
418
+ local DISK_ADDRESS="$4"
419
+
420
+ local index=""
421
+ local DISK_ID="cdrom$DISK_INDEX"
422
+ [ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
423
+ local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=raw,cache=unsafe,readonly=on,media=cdrom"
424
+
425
+ case "${DISK_TYPE,,}" in
426
+ "none" ) ;;
427
+ "auto" )
428
+ echo "$result"
429
+ ;;
430
+ "usb" )
431
+ result+=",if=none \
432
+ -device usb-storage,drive=${DISK_ID}${index},removable=on"
433
+ echo "$result"
434
+ ;;
435
+ "nvme" )
436
+ result+=",if=none \
437
+ -device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}"
438
+ echo "$result"
439
+ ;;
440
+ "ide" | "sata" )
441
+ result+=",if=none \
442
+ -device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
443
+ -device ide-cd,drive=${DISK_ID},bus=ahci${DISK_INDEX}.0${index}"
444
+ echo "$result"
445
+ ;;
446
+ "blk" | "virtio-blk" )
447
+ result+=",if=none \
448
+ -device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}"
449
+ echo "$result"
450
+ ;;
451
+ "scsi" | "virtio-scsi" )
452
+ result+=",if=none \
453
+ -device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
454
+ -device scsi-cd,drive=${DISK_ID},bus=${DISK_ID}b.0${index}"
455
+ echo "$result"
456
+ ;;
457
+ esac
458
+
459
+ return 0
460
+ }
461
+
462
+ addDisk () {
463
+
464
+ local DISK_BASE="$1"
465
+ local DISK_TYPE="$2"
466
+ local DISK_DESC="$3"
467
+ local DISK_SPACE="$4"
468
+ local DISK_INDEX="$5"
469
+ local DISK_ADDRESS="$6"
470
+ local DISK_FMT="$7"
471
+ local DISK_IO="$8"
472
+ local DISK_CACHE="$9"
473
+ local DISK_EXT DIR SPACE GB DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE LEFT FREE USED
474
+
475
+ DISK_EXT=$(fmt2ext "$DISK_FMT")
476
+ local DISK_FILE="$DISK_BASE.$DISK_EXT"
477
+
478
+ DIR=$(dirname "$DISK_FILE")
479
+ [ ! -d "$DIR" ] && return 0
480
+
481
+ if [[ "${DISK_SPACE,,}" == "max" || "${DISK_SPACE,,}" == "half" ]]; then
482
+
483
+ local SPARE=1073741824
484
+ FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
485
+
486
+ if [[ "${DISK_SPACE,,}" == "max" ]]; then
487
+ FREE=$(( FREE - SPARE ))
488
+ else
489
+ FREE=$(( FREE / 2 ))
490
+ fi
491
+
492
+ (( FREE < SPARE )) && FREE="$SPARE"
493
+ GB=$(( FREE / 1073741825 ))
494
+ DISK_SPACE="${GB}G"
495
+
496
+ fi
497
+
498
+ SPACE="${DISK_SPACE// /}"
499
+ [ -z "$SPACE" ] && SPACE="64G"
500
+ [ -z "${SPACE//[0-9. ]}" ] && SPACE="${SPACE}G"
501
+ SPACE=$(echo "${SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
502
+
503
+ if ! numfmt --from=iec "$SPACE" &>/dev/null; then
504
+ error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
505
+ fi
506
+
507
+ DATA_SIZE=$(numfmt --from=iec "$SPACE")
508
+
509
+ if (( DATA_SIZE < 104857600 )); then
510
+ error "Please increase the ${DISK_DESC^^}_SIZE variable to at least 100 MB." && exit 73
511
+ fi
512
+
513
+ FS=$(stat -f -c %T "$DIR")
514
+ checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
515
+
516
+ if ! supportsDirect "$FS"; then
517
+ DISK_IO="threads"
518
+ DISK_CACHE="writeback"
519
+ fi
520
+
521
+ if [ ! -s "$DISK_FILE" ] ; then
522
+
523
+ if [[ "${DISK_FMT,,}" != "raw" ]]; then
524
+ PREV_FMT="raw"
525
+ else
526
+ PREV_FMT="qcow2"
527
+ fi
528
+
529
+ PREV_EXT=$(fmt2ext "$PREV_FMT")
530
+
531
+ if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
532
+ convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
533
+ fi
534
+
535
+ fi
536
+
537
+ if [ -s "$DISK_FILE" ]; then
538
+
539
+ CUR_SIZE=$(getSize "$DISK_FILE")
540
+
541
+ if (( DATA_SIZE > CUR_SIZE )); then
542
+
543
+ resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
544
+
545
+ else
546
+
547
+ if (( DATA_SIZE < CUR_SIZE )); then
548
+
549
+ if [[ "${DISK_SPACE,,}" != "max" && "${DISK_SPACE,,}" != "half" ]]; then
550
+ info "You decreased the ${DISK_DESC^^}_SIZE variable to ${DISK_SPACE/G/ GB} but shrinking disks is not supported, will be ignored..."
551
+ fi
552
+
553
+ fi
554
+ fi
555
+
556
+ else
557
+
558
+ createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
559
+
560
+ fi
561
+
562
+ if [ -f "$DISK_FILE" ] && [[ "$ALLOCATE" == [Nn]* ]]; then
563
+
564
+ CUR_SIZE=$(getSize "$DISK_FILE")
565
+ USED=$(du -sB 1 "$DISK_FILE" | cut -f1)
566
+ FREE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
567
+ LEFT=$(( CUR_SIZE - USED - FREE ))
568
+
569
+ if (( LEFT > 0 )); then
570
+
571
+ GB=$(formatBytes "$FREE")
572
+ LEFT=$(formatBytes "$LEFT")
573
+ CUR_SIZE=$(formatBytes "$CUR_SIZE")
574
+ msg="the virtual size of the ${DISK_DESC,,} is $CUR_SIZE"
575
+
576
+ if [[ "$USED" == "0" ]]; then
577
+ msg+=","
578
+ else
579
+ USED=$(formatBytes "$USED")
580
+ msg+=" (of which $USED is used),"
581
+ fi
582
+
583
+ warn "$msg but there is only $GB of free space left in $DIR, make at least $LEFT more room available!"
584
+
585
+ fi
586
+
587
+ fi
588
+
589
+ if [ -f "$DISK_FILE" ]; then
590
+ if ! setOwner "$DISK_FILE"; then
591
+ error "Failed to set the owner for \"$DISK_FILE\" !"
592
+ fi
593
+ fi
594
+
595
+ DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" "" "")
596
+
597
+ return 0
598
+ }
599
+
600
+ addDevice () {
601
+
602
+ local DISK_DEV="$1"
603
+ local DISK_TYPE="$2"
604
+ local DISK_INDEX="$3"
605
+ local DISK_ADDRESS="$4"
606
+
607
+ [ -z "$DISK_DEV" ] && return 0
608
+ [ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
609
+
610
+ local sectors=""
611
+ local result logical physical
612
+ result=$(fdisk -l "$DISK_DEV" | grep -m 1 -o "(logical/physical): .*" | cut -c 21-)
613
+ logical="${result%% *}"
614
+ physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3-)
615
+ physical="${physical%% *}"
616
+
617
+ if [ -n "$physical" ]; then
618
+ if [[ "$physical" != "512" ]]; then
619
+ sectors=",logical_block_size=$logical,physical_block_size=$physical"
620
+ fi
621
+ else
622
+ warn "Failed to determine the sector size for $DISK_DEV"
623
+ fi
624
+
625
+ DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
626
+
627
+ return 0
628
+ }
629
+
630
+ msg="Initializing disks..."
631
+ html "$msg"
632
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
633
+
634
+ [ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
635
+ [ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
636
+ [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
637
+
638
+ case "${DISK_TYPE,,}" in
639
+ "ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
640
+ * ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
641
+ esac
642
+
643
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
644
+ FALLBACK="ide"
645
+ else
646
+ FALLBACK="usb"
647
+ fi
648
+
649
+ [[ "${BOOT_MODE:-}" == "windows_legacy" ]] && FALLBACK="auto"
650
+
651
+ if [ -z "${MEDIA_TYPE:-}" ]; then
652
+ if [[ "${BOOT_MODE:-}" != "windows"* ]]; then
653
+ if [[ "${DISK_TYPE,,}" == "blk" ]]; then
654
+ MEDIA_TYPE="$FALLBACK"
655
+ else
656
+ MEDIA_TYPE="$DISK_TYPE"
657
+ fi
658
+ else
659
+ MEDIA_TYPE="$FALLBACK"
660
+ fi
661
+ fi
662
+
663
+ case "${MEDIA_TYPE,,}" in
664
+ "ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
665
+ * ) error "Invalid MEDIA_TYPE specified, value \"$MEDIA_TYPE\" is not recognized!" && exit 80 ;;
666
+ esac
667
+
668
+ if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
669
+ case "${BOOT,,}" in
670
+ *".iso" )
671
+ if [[ "${BOOT_MODE:-}" == "windows"* ]]; then
672
+ hybrid="0000"
673
+ else
674
+ hybrid=$(head -c 512 "$BOOT" | tail -c 2 | xxd -p)
675
+ fi
676
+ if [[ "$hybrid" != "0000" ]]; then
677
+ DISK_OPTS+=$(addMedia "$BOOT" "usb" "$BOOT_INDEX" "0x5")
678
+ else
679
+ DISK_OPTS+=$(addMedia "$BOOT" "$MEDIA_TYPE" "$BOOT_INDEX" "0x5")
680
+ fi ;;
681
+ *".img" | *".raw" )
682
+ DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "raw" "$DISK_IO" "$DISK_CACHE" "" "") ;;
683
+ *".qcow2" )
684
+ DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "qcow2" "$DISK_IO" "$DISK_CACHE" "" "") ;;
685
+ * )
686
+ error "Invalid BOOT image specified, extension \".${BOOT/*./}\" is not recognized!" && exit 80 ;;
687
+ esac
688
+ fi
689
+
690
+ DRIVERS="/drivers.iso"
691
+ [ ! -f "$DRIVERS" ] || [ ! -s "$DRIVERS" ] && DRIVERS="$STORAGE/drivers.iso"
692
+
693
+ if [ -f "$DRIVERS" ] && [ -s "$DRIVERS" ]; then
694
+ DISK_OPTS+=$(addMedia "$DRIVERS" "$FALLBACK" "" "0x6")
695
+ fi
696
+
697
+ RESCUE="/start.iso"
698
+ [ ! -f "$RESCUE" ] || [ ! -s "$RESCUE" ] && RESCUE="$STORAGE/start.iso"
699
+
700
+ if [ -f "$RESCUE" ] && [ -s "$RESCUE" ]; then
701
+ DISK_OPTS+=$(addMedia "$RESCUE" "$FALLBACK" "1" "0x6")
702
+ fi
703
+
704
+ DISK1_FILE="$STORAGE/${DISK_NAME}"
705
+ DISK2_FILE="/storage2/${DISK_NAME}2"
706
+ DISK3_FILE="/storage3/${DISK_NAME}3"
707
+ DISK4_FILE="/storage4/${DISK_NAME}4"
708
+ DISK5_FILE="/storage5/${DISK_NAME}5"
709
+ DISK6_FILE="/storage6/${DISK_NAME}6"
710
+
711
+ if [ -z "$DISK_FMT" ]; then
712
+ if [ -f "$DISK1_FILE.qcow2" ]; then
713
+ DISK_FMT="qcow2"
714
+ else
715
+ DISK_FMT="raw"
716
+ fi
717
+ fi
718
+
719
+ if [ -z "$ALLOCATE" ]; then
720
+ ALLOCATE="N"
721
+ fi
722
+
723
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
724
+ DISK_STYLE="growable"
725
+ DISK_ALLOC="preallocation=off"
726
+ else
727
+ DISK_STYLE="preallocated"
728
+ DISK_ALLOC="preallocation=falloc"
729
+ fi
730
+
731
+ : "${DISK2_SIZE:=""}"
732
+ : "${DISK3_SIZE:=""}"
733
+ : "${DISK4_SIZE:=""}"
734
+ : "${DISK5_SIZE:=""}"
735
+ : "${DISK6_SIZE:=""}"
736
+
737
+ : "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
738
+ : "${DEVICE2:=""}"
739
+ : "${DEVICE3:=""}"
740
+ : "${DEVICE4:=""}"
741
+ : "${DEVICE5:=""}"
742
+ : "${DEVICE6:=""}"
743
+
744
+ [ -z "$DEVICE" ] && [ -b "/disk" ] && DEVICE="/disk"
745
+ [ -z "$DEVICE" ] && [ -b "/disk1" ] && DEVICE="/disk1"
746
+ [ -z "$DEVICE2" ] && [ -b "/disk2" ] && DEVICE2="/disk2"
747
+ [ -z "$DEVICE3" ] && [ -b "/disk3" ] && DEVICE3="/disk3"
748
+ [ -z "$DEVICE4" ] && [ -b "/disk4" ] && DEVICE4="/disk4"
749
+ [ -z "$DEVICE5" ] && [ -b "/disk5" ] && DEVICE5="/disk5"
750
+ [ -z "$DEVICE6" ] && [ -b "/disk6" ] && DEVICE6="/disk6"
751
+
752
+ [ -z "$DEVICE" ] && [ -b "/dev/disk1" ] && DEVICE="/dev/disk1"
753
+ [ -z "$DEVICE2" ] && [ -b "/dev/disk2" ] && DEVICE2="/dev/disk2"
754
+ [ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
755
+ [ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
756
+ [ -z "$DEVICE5" ] && [ -b "/dev/disk5" ] && DEVICE4="/dev/disk5"
757
+ [ -z "$DEVICE6" ] && [ -b "/dev/disk6" ] && DEVICE4="/dev/disk6"
758
+
759
+ if [ -n "$DEVICE" ]; then
760
+ addDevice "$DEVICE" "$DISK_TYPE" "3" "0xa" || exit $?
761
+ else
762
+ addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xa" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
763
+ fi
764
+
765
+ if [ -n "$DEVICE2" ]; then
766
+ addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xb" || exit $?
767
+ else
768
+ addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xb" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
769
+ fi
770
+
771
+ if [ -n "$DEVICE3" ]; then
772
+ addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xc" || exit $?
773
+ else
774
+ addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
775
+ fi
776
+
777
+ if [ -n "$DEVICE4" ]; then
778
+ addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xd" || exit $?
779
+ else
780
+ addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
781
+ fi
782
+
783
+ if [ -n "$DEVICE5" ]; then
784
+ addDevice "$DEVICE5" "$DISK_TYPE" "7" "0xe" || exit $?
785
+ else
786
+ addDisk "$DISK5_FILE" "$DISK_TYPE" "disk5" "$DISK5_SIZE" "7" "0xe" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
787
+ fi
788
+
789
+ if [ -n "$DEVICE6" ]; then
790
+ addDevice "$DEVICE6" "$DISK_TYPE" "8" "0xf" || exit $?
791
+ else
792
+ addDisk "$DISK6_FILE" "$DISK_TYPE" "disk6" "$DISK6_SIZE" "8" "0xf" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
793
+ fi
794
+
795
+ DISK_OPTS+=" -object iothread,id=io2"
796
+
797
+ html "Initialized disks successfully..."
798
+ return 0
src/display.sh ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+
6
+ : "${GPU:="N"}" # GPU passthrough
7
+ : "${VGA:="virtio"}" # VGA adaptor
8
+ : "${DISPLAY:="web"}" # Display type
9
+ : "${RENDERNODE:="/dev/dri/renderD128"}" # Render node
10
+
11
+ port=$(( VNC_PORT - 5900 ))
12
+ [[ "$DISPLAY" == ":0" ]] && DISPLAY="web"
13
+
14
+ case "${DISPLAY,,}" in
15
+ "vnc" )
16
+ DISPLAY_OPTS="-display vnc=:$port -vga $VGA"
17
+ ;;
18
+ "web" )
19
+ DISPLAY_OPTS="-display vnc=:$port,websocket=$WSS_PORT -vga $VGA"
20
+ ;;
21
+ "disabled" )
22
+ DISPLAY_OPTS="-display none -vga $VGA"
23
+ ;;
24
+ "none" )
25
+ DISPLAY_OPTS="-display none -vga none"
26
+ ;;
27
+ *)
28
+ DISPLAY_OPTS="-display $DISPLAY -vga $VGA"
29
+ ;;
30
+ esac
31
+
32
+ if [[ "$GPU" != [Yy1]* || "$ARCH" != "amd64" ]]; then
33
+ return 0
34
+ fi
35
+
36
+ CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
37
+
38
+ if [[ "$CPU_VENDOR" != "GenuineIntel" ]]; then
39
+ return 0
40
+ fi
41
+
42
+ msg="Configuring display drivers..."
43
+ html "$msg"
44
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
45
+
46
+ [[ "${VGA,,}" == "virtio" ]] && VGA="virtio-vga-gl"
47
+ DISPLAY_OPTS="-display egl-headless,rendernode=$RENDERNODE"
48
+ DISPLAY_OPTS+=" -device $VGA"
49
+
50
+ [[ "${DISPLAY,,}" == "vnc" ]] && DISPLAY_OPTS+=" -vnc :$port"
51
+ [[ "${DISPLAY,,}" == "web" ]] && DISPLAY_OPTS+=" -vnc :$port,websocket=$WSS_PORT"
52
+
53
+ [ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
54
+
55
+ # Extract the card number from the render node
56
+ CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
57
+ CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
58
+
59
+ if [ ! -c "$CARD_DEVICE" ]; then
60
+ if mknod "$CARD_DEVICE" c 226 $((CARD_NUMBER - 128)); then
61
+ chmod 666 "$CARD_DEVICE"
62
+ fi
63
+ fi
64
+
65
+ if [ ! -c "$RENDERNODE" ]; then
66
+ if mknod "$RENDERNODE" c 226 "$CARD_NUMBER"; then
67
+ chmod 666 "$RENDERNODE"
68
+ fi
69
+ fi
70
+
71
+ addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
72
+ addPackage "qemu-system-modules-opengl" "OpenGL module"
73
+
74
+ return 0
src/entry.sh ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ : "${APP:="QEMU"}"
5
+ : "${PLATFORM:="x64"}"
6
+ : "${SUPPORT:="https://github.com/qemus/qemu"}"
7
+
8
+ cd /run
9
+
10
+ . start.sh # Startup hook
11
+ . utils.sh # Load functions
12
+ . reset.sh # Initialize system
13
+ . server.sh # Start webserver
14
+ . define.sh # Define images
15
+ . install.sh # Download image
16
+ . disk.sh # Initialize disks
17
+ . display.sh # Initialize graphics
18
+ . network.sh # Initialize network
19
+ . boot.sh # Configure boot
20
+ . proc.sh # Initialize processor
21
+ . memory.sh # Check available memory
22
+ . config.sh # Configure arguments
23
+ . finish.sh # Finish initialization
24
+
25
+ trap - ERR
26
+
27
+ version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }')
28
+ info "Booting image${BOOT_DESC} using QEMU v$version..."
29
+
30
+ exec qemu-system-x86_64 ${ARGS:+ $ARGS}
src/finish.sh ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ if [[ "${DISPLAY,,}" == "web" ]]; then
5
+ [ ! -f "$INFO" ] && error "File $INFO not found?!"
6
+ rm -f "$INFO"
7
+ [ ! -f "$PAGE" ] && error "File $PAGE not found?!"
8
+ rm -f "$PAGE"
9
+ else
10
+ if [[ "${DISPLAY,,}" == "vnc" ]]; then
11
+ html "You can now connect to VNC on port $VNC_PORT." "0"
12
+ else
13
+ html "The virtual machine was booted successfully." "0"
14
+ fi
15
+ fi
16
+
17
+ if [[ "$DEBUG" == [Yy1]* ]]; then
18
+ printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
19
+ fi
20
+
21
+ return 0
src/install.sh ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ getBase() {
5
+
6
+ local base="${1%%\?*}"
7
+ base=$(basename "$base")
8
+ base="${base//+/ }"
9
+ printf -v base '%b' "${base//%/\\x}"
10
+ base="${base//[!A-Za-z0-9._-]/_}"
11
+
12
+ echo "$base"
13
+ return 0
14
+ }
15
+
16
+ getFolder() {
17
+
18
+ local base=""
19
+ local result="$1"
20
+
21
+ if [[ "$result" != *"."* ]]; then
22
+
23
+ result="${result,,}"
24
+
25
+ else
26
+
27
+ base=$(getBase "$result")
28
+ result="${base%.*}"
29
+
30
+ case "${base,,}" in
31
+
32
+ *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
33
+
34
+ [[ "$result" == *"."* ]] && result="${result%.*}" ;;
35
+
36
+ esac
37
+
38
+ fi
39
+
40
+ [ -z "$result" ] && result="unknown"
41
+ echo "$result"
42
+
43
+ return 0
44
+ }
45
+
46
+ moveFile() {
47
+
48
+ local file="$1"
49
+ local ext="${file##*.}"
50
+ local dest="$STORAGE/boot.$ext"
51
+
52
+ if [[ "$file" == "$dest" ]]; then
53
+ BOOT="$file"
54
+ return 0
55
+ fi
56
+
57
+ if [[ "${file,,}" == "/boot.${ext,,}" || "${file,,}" == "/custom.${ext,,}" ]]; then
58
+ BOOT="$file"
59
+ return 0
60
+ fi
61
+
62
+ if ! mv -f "$file" "$dest"; then
63
+ error "Failed to move $file to $dest !"
64
+ return 1
65
+ fi
66
+
67
+ BOOT="$dest"
68
+ return 0
69
+ }
70
+
71
+ detectType() {
72
+
73
+ local file="$1"
74
+ local result=""
75
+ local hybrid=""
76
+
77
+ [ ! -f "$file" ] && return 1
78
+ [ ! -s "$file" ] && return 1
79
+
80
+ case "${file,,}" in
81
+ *".iso" | *".img" | *".raw" | *".qcow2" ) ;;
82
+ * ) return 1 ;;
83
+ esac
84
+
85
+ if [ -n "$BOOT_MODE" ] || [[ "${file,,}" == *".qcow2" ]]; then
86
+ moveFile "$file" && return 0
87
+ return 1
88
+ fi
89
+
90
+ if [[ "${file,,}" == *".iso" ]]; then
91
+
92
+ hybrid=$(head -c 512 "$file" | tail -c 2 | xxd -p)
93
+
94
+ if [[ "$hybrid" != "0000" ]]; then
95
+
96
+ result=$(isoinfo -f -i "$file" 2>/dev/null)
97
+
98
+ if [ -z "$result" ]; then
99
+ error "Failed to read ISO file, invalid format!"
100
+ return 1
101
+ fi
102
+
103
+ result=$(echo "${result^^}" | grep "^/EFI")
104
+ [ -z "$result" ] && BOOT_MODE="legacy"
105
+
106
+ moveFile "$file" && return 0
107
+ return 1
108
+
109
+ fi
110
+ fi
111
+
112
+ result=$(fdisk -l "$file" 2>/dev/null)
113
+ [[ "${result^^}" != *"EFI "* ]] && BOOT_MODE="legacy"
114
+
115
+ moveFile "$file" && return 0
116
+ return 1
117
+ }
118
+
119
+ delay() {
120
+
121
+ local i
122
+ local delay="$1"
123
+ local msg="Retrying failed download in X seconds..."
124
+
125
+ info "${msg/X/$delay}"
126
+
127
+ for i in $(seq "$delay" -1 1); do
128
+ html "${msg/X/$i}"
129
+ sleep 1
130
+ done
131
+
132
+ return 0
133
+ }
134
+
135
+ downloadFile() {
136
+
137
+ local url="$1"
138
+ local base="$2"
139
+ local name="$3"
140
+ local msg rc total size progress
141
+
142
+ local dest="$STORAGE/$base"
143
+
144
+ # Check if running with interactive TTY or redirected to docker log
145
+ if [ -t 1 ]; then
146
+ progress="--progress=bar:noscroll"
147
+ else
148
+ progress="--progress=dot:giga"
149
+ fi
150
+
151
+ if [ -z "$name" ]; then
152
+ msg="Downloading image"
153
+ info "Downloading $base..."
154
+ else
155
+ msg="Downloading $name"
156
+ info "Downloading $name..."
157
+ fi
158
+
159
+ html "$msg..."
160
+
161
+ /run/progress.sh "$dest" "0" "$msg ([P])..." &
162
+
163
+ { wget "$url" -O "$dest" --continue -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || :
164
+
165
+ fKill "progress.sh"
166
+
167
+ if (( rc == 0 )) && [ -f "$dest" ]; then
168
+ total=$(stat -c%s "$dest")
169
+ size=$(formatBytes "$total")
170
+ if [ "$total" -lt 100000 ]; then
171
+ error "Invalid image file: is only $size ?" && return 1
172
+ fi
173
+ html "Download finished successfully..."
174
+ return 0
175
+ fi
176
+
177
+ msg="Failed to download $url"
178
+ (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
179
+ (( rc == 4 )) && error "$msg , network failure!" && return 1
180
+ (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
181
+
182
+ error "$msg , reason: $rc"
183
+ return 1
184
+ }
185
+
186
+ convertImage() {
187
+
188
+ local source_file=$1
189
+ local source_fmt=$2
190
+ local dst_file=$3
191
+ local dst_fmt=$4
192
+ local dir base fs fa space space_gb
193
+ local cur_size cur_gb src_size disk_param
194
+
195
+ [ -f "$dst_file" ] && error "Conversion failed, destination file $dst_file already exists?" && return 1
196
+ [ ! -f "$source_file" ] && error "Conversion failed, source file $source_file does not exists?" && return 1
197
+
198
+ if [[ "${source_fmt,,}" == "${dst_fmt,,}" ]]; then
199
+ mv -f "$source_file" "$dst_file"
200
+ return 0
201
+ fi
202
+
203
+ local tmp_file="$dst_file.tmp"
204
+ dir=$(dirname "$tmp_file")
205
+
206
+ rm -f "$tmp_file"
207
+
208
+ if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
209
+
210
+ # Check free diskspace
211
+ src_size=$(qemu-img info "$source_file" -f "$source_fmt" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/')
212
+ space=$(df --output=avail -B 1 "$dir" | tail -n 1)
213
+
214
+ if (( src_size > space )); then
215
+ space_gb=$(formatBytes "$space")
216
+ error "Not enough free space to convert image in $dir, it has only $space_gb available..." && return 1
217
+ fi
218
+ fi
219
+
220
+ base=$(basename "$source_file")
221
+ info "Converting $base..."
222
+ html "Converting image..."
223
+
224
+ local conv_flags="-p"
225
+
226
+ if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
227
+ disk_param="preallocation=off"
228
+ else
229
+ disk_param="preallocation=falloc"
230
+ fi
231
+
232
+ fs=$(stat -f -c %T "$dir")
233
+ [[ "${fs,,}" == "btrfs" ]] && disk_param+=",nocow=on"
234
+
235
+ if [[ "$dst_fmt" != "raw" ]]; then
236
+ if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
237
+ conv_flags+=" -c"
238
+ fi
239
+ [ -n "${DISK_FLAGS:-}" ] && disk_param+=",$DISK_FLAGS"
240
+ fi
241
+
242
+ # shellcheck disable=SC2086
243
+ if ! qemu-img convert -f "$source_fmt" $conv_flags -o "$disk_param" -O "$dst_fmt" -- "$source_file" "$tmp_file"; then
244
+ rm -f "$tmp_file"
245
+ error "Failed to convert image in $dir, is there enough space available?" && return 1
246
+ fi
247
+
248
+ if [[ "$dst_fmt" == "raw" ]]; then
249
+ if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
250
+ # Work around qemu-img bug
251
+ cur_size=$(stat -c%s "$tmp_file")
252
+ cur_gb=$(formatBytes "$cur_size")
253
+ if ! fallocate -l "$cur_size" "$tmp_file" &>/dev/null; then
254
+ if ! fallocate -l -x "$cur_size" "$tmp_file"; then
255
+ error "Failed to allocate $cur_gb for image!"
256
+ fi
257
+ fi
258
+ fi
259
+ fi
260
+
261
+ rm -f "$source_file"
262
+ mv "$tmp_file" "$dst_file"
263
+
264
+ if [[ "${fs,,}" == "btrfs" ]]; then
265
+ fa=$(lsattr "$dst_file")
266
+ if [[ "$fa" != *"C"* ]]; then
267
+ error "Failed to disable COW for image on ${fs^^} filesystem!"
268
+ fi
269
+ fi
270
+
271
+ html "Conversion completed..."
272
+ return 0
273
+ }
274
+
275
+ findFile() {
276
+
277
+ local dir file
278
+ local base="$1"
279
+ local ext="$2"
280
+ local fname="${base}.${ext}"
281
+
282
+ dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit)
283
+ [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit)
284
+
285
+ if [ -d "$dir" ]; then
286
+ if hasDisk; then
287
+ BOOT="none"
288
+ return 0
289
+ fi
290
+ error "The bind $dir maps to a file that does not exist!" && exit 37
291
+ fi
292
+
293
+ file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit)
294
+ [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit)
295
+
296
+ detectType "$file" && return 0
297
+
298
+ return 1
299
+ }
300
+
301
+ findFile "boot" "img" && return 0
302
+ findFile "boot" "raw" && return 0
303
+ findFile "boot" "iso" && return 0
304
+ findFile "boot" "qcow2" && return 0
305
+ findFile "custom" "iso" && return 0
306
+
307
+ if hasDisk; then
308
+ BOOT="none"
309
+ return 0
310
+ fi
311
+
312
+ if [[ "${BOOT}" == \"*\" || "${BOOT}" == \'*\' ]]; then
313
+ VERSION="${BOOT:1:-1}"
314
+ fi
315
+
316
+ BOOT=$(expr "$BOOT" : "^\ *\(.*[^ ]\)\ *$")
317
+
318
+ if [ -z "$BOOT" ] || [[ "$BOOT" == *"example.com/"* ]]; then
319
+
320
+ BOOT="alpine"
321
+ warn "no value specified for the BOOT variable, defaulting to \"${BOOT}\"."
322
+
323
+ fi
324
+
325
+ folder=$(getFolder "$BOOT")
326
+ STORAGE="$STORAGE/$folder"
327
+
328
+ if [ -d "$STORAGE" ]; then
329
+
330
+ findFile "boot" "img" && return 0
331
+ findFile "boot" "raw" && return 0
332
+ findFile "boot" "iso" && return 0
333
+ findFile "boot" "qcow2" && return 0
334
+ findFile "custom" "iso" && return 0
335
+
336
+ if hasDisk; then
337
+ BOOT="none"
338
+ return 0
339
+ fi
340
+
341
+ fi
342
+
343
+ name=$(getURL "$BOOT" "name") || exit 34
344
+
345
+ if [ -n "$name" ]; then
346
+
347
+ msg="Retrieving latest $name version..."
348
+ info "$msg" && html "$msg..."
349
+
350
+ url=$(getURL "$BOOT" "url") || exit 34
351
+
352
+ [ -n "$url" ] && BOOT="$url"
353
+
354
+ fi
355
+
356
+ if [[ "$BOOT" != *"."* ]]; then
357
+ if [ -z "$BOOT" ]; then
358
+ error "No BOOT value specified!"
359
+ else
360
+ error "Invalid BOOT value specified, option \"$BOOT\" is not recognized!"
361
+ fi
362
+ exit 64
363
+ fi
364
+
365
+ if [[ "${BOOT,,}" != "http"* ]]; then
366
+ error "Invalid BOOT value specified, \"$BOOT\" is not a valid URL!" && exit 64
367
+ fi
368
+
369
+ if ! makeDir "$STORAGE"; then
370
+ error "Failed to create directory \"$STORAGE\" !" && exit 33
371
+ fi
372
+
373
+ find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete
374
+ find "$STORAGE" -maxdepth 1 -type f \( -iname 'data.*' -or -iname 'qemu.*' \) -delete
375
+
376
+ base=$(getBase "$BOOT")
377
+
378
+ rm -f "$STORAGE/$base"
379
+
380
+ if ! downloadFile "$BOOT" "$base" "$name"; then
381
+ delay 5
382
+ if ! downloadFile "$BOOT" "$base" "$name"; then
383
+ delay 10
384
+ if ! downloadFile "$BOOT" "$base" "$name"; then
385
+ rm -f "$STORAGE/$base" && exit 60
386
+ fi
387
+ fi
388
+ fi
389
+
390
+ case "${base,,}" in
391
+ *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
392
+ info "Extracting $base..."
393
+ html "Extracting image..." ;;
394
+ esac
395
+
396
+ case "${base,,}" in
397
+ *".gz" | *".gzip" )
398
+
399
+ gzip -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
400
+ rm -f "$STORAGE/$base"
401
+ base="${base%.*}"
402
+
403
+ ;;
404
+ *".xz" )
405
+
406
+ xz -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
407
+ rm -f "$STORAGE/$base"
408
+ base="${base%.*}"
409
+
410
+ ;;
411
+ *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
412
+
413
+ tmp="$STORAGE/extract"
414
+ rm -rf "$tmp"
415
+
416
+ if ! makeDir "$tmp"; then
417
+ error "Failed to create directory \"$tmp\" !" && exit 33
418
+ fi
419
+
420
+ 7z x "$STORAGE/$base" -o"$tmp" > /dev/null
421
+
422
+ rm -f "$STORAGE/$base"
423
+ base="${base%.*}"
424
+
425
+ if [ ! -s "$tmp/$base" ]; then
426
+ rm -rf "$tmp"
427
+ error "Cannot find file \"${base}\" in .${BOOT/*./} archive!" && exit 32
428
+ fi
429
+
430
+ mv "$tmp/$base" "$STORAGE/$base"
431
+ rm -rf "$tmp"
432
+
433
+ ;;
434
+ esac
435
+
436
+ case "${base,,}" in
437
+ *".iso" | *".img" | *".raw" | *".qcow2" )
438
+
439
+ ! setOwner "$STORAGE/$base" && error "Failed to set the owner for \"$STORAGE/$base\" !"
440
+ detectType "$STORAGE/$base" && return 0
441
+ error "Cannot read file \"${base}\"" && exit 63 ;;
442
+ esac
443
+
444
+ target_ext="img"
445
+ target_fmt="${DISK_FMT:-}"
446
+ [ -z "$target_fmt" ] && target_fmt="raw"
447
+ [[ "$target_fmt" != "raw" ]] && target_ext="qcow2"
448
+
449
+ case "${base,,}" in
450
+ *".vdi" ) source_fmt="vdi" ;;
451
+ *".vhd" ) source_fmt="vpc" ;;
452
+ *".vhdx" ) source_fmt="vpc" ;;
453
+ *".vmdk" ) source_fmt="vmdk" ;;
454
+ * ) error "Unknown file extension, type \".${base/*./}\" is not recognized!" && exit 33 ;;
455
+ esac
456
+
457
+ dst="$STORAGE/${base%.*}.$target_ext"
458
+
459
+ ! convertImage "$STORAGE/$base" "$source_fmt" "$dst" "$target_fmt" && exit 35
460
+
461
+ base=$(basename "$dst")
462
+
463
+ ! setOwner "$STORAGE/$base" && error "Failed to set the owner for \"$STORAGE/$base\" !"
464
+ detectType "$STORAGE/$base" && return 0
465
+ error "Cannot convert file \"${base}\"" && exit 36
466
+
467
+ return 0
src/memory.sh ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ msg="Checking memory..."
5
+ html "$msg"
6
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
7
+
8
+ RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
9
+
10
+ if [[ "$RAM_CHECK" != [Nn]* && "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
11
+
12
+ AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
13
+
14
+ if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
15
+ msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of memory available,"
16
+ if [[ "${FS,,}" == "zfs" ]]; then
17
+ info "$msg but since ZFS is active this will be ignored."
18
+ else
19
+ RAM_SIZE="max"
20
+ warn "$msg it will automatically be adjusted to a lower amount."
21
+ fi
22
+ else
23
+ if (( (RAM_WANTED + (RAM_SPARE * 3)) > RAM_AVAIL )); then
24
+ msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $AVAIL_MEM of memory available,"
25
+ if [[ "${FS,,}" == "zfs" ]]; then
26
+ info "$msg but since ZFS is active this will be ignored."
27
+ else
28
+ warn "$msg please consider a lower amount."
29
+ fi
30
+ fi
31
+ fi
32
+
33
+ fi
34
+
35
+ if [[ "${RAM_SIZE,,}" == "half" ]]; then
36
+
37
+ RAM_WANTED=$(( RAM_AVAIL / 2 ))
38
+ RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
39
+
40
+ if (( "$RAM_WANTED" < 1 )); then
41
+ RAM_WANTED=$(( RAM_AVAIL / 2 ))
42
+ RAM_WANTED=$(( RAM_WANTED / 1048577 ))
43
+ RAM_SIZE="${RAM_WANTED}M"
44
+ else
45
+ RAM_SIZE="${RAM_WANTED}G"
46
+ fi
47
+
48
+ fi
49
+
50
+ if [[ "${RAM_SIZE,,}" == "max" ]]; then
51
+
52
+ RAM_WANTED=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
53
+ RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
54
+
55
+ if (( "$RAM_WANTED" < 1 )); then
56
+
57
+ RAM_WANTED=$(( RAM_AVAIL - (RAM_SPARE * 2) ))
58
+ RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
59
+
60
+ if (( "$RAM_WANTED" < 1 )); then
61
+
62
+ RAM_WANTED=$(( RAM_AVAIL - RAM_SPARE ))
63
+ RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
64
+
65
+ if (( "$RAM_WANTED" < 1 )); then
66
+
67
+ RAM_WANTED=$(( RAM_AVAIL - RAM_SPARE ))
68
+ RAM_WANTED=$(( RAM_WANTED / 1048577 ))
69
+
70
+ if (( "$RAM_WANTED" < 1 )); then
71
+
72
+ RAM_WANTED=$(( RAM_AVAIL ))
73
+ RAM_WANTED=$(( RAM_WANTED / 1048577 ))
74
+
75
+ fi
76
+
77
+ RAM_SIZE="${RAM_WANTED}M"
78
+ else
79
+ RAM_SIZE="${RAM_WANTED}G"
80
+ fi
81
+ else
82
+ RAM_SIZE="${RAM_WANTED}G"
83
+ fi
84
+ else
85
+ RAM_SIZE="${RAM_WANTED}G"
86
+ fi
87
+
88
+ fi
89
+
90
+ return 0
src/network.sh ADDED
@@ -0,0 +1,891 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+
6
+ : "${MAC:=""}"
7
+ : "${MTU:=""}"
8
+ : "${DHCP:="N"}"
9
+ : "${NETWORK:="Y"}"
10
+ : "${HOST_PORTS:=""}"
11
+ : "${USER_PORTS:=""}"
12
+ : "${ADAPTER:="virtio-net-pci"}"
13
+
14
+ : "${VM_NET_IP:=""}"
15
+ : "${VM_NET_DEV:=""}"
16
+ : "${VM_NET_TAP:="qemu"}"
17
+ : "${VM_NET_MAC:="$MAC"}"
18
+ : "${VM_NET_HOST:="$APP"}"
19
+ : "${VM_NET_BRIDGE:="docker"}"
20
+ : "${VM_NET_MASK:="255.255.255.0"}"
21
+
22
+ : "${PASST:="passt"}"
23
+ : "${PASST_MTU:=""}"
24
+ : "${PASST_OPTS:=""}"
25
+ : "${PASST_DEBUG:=""}"
26
+
27
+ : "${DNSMASQ_OPTS:=""}"
28
+ : "${DNSMASQ_DEBUG:=""}"
29
+ : "${DNSMASQ:="/usr/sbin/dnsmasq"}"
30
+ : "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
31
+
32
+ ADD_ERR="Please add the following setting to your container:"
33
+
34
+ # ######################################
35
+ # Functions
36
+ # ######################################
37
+
38
+ configureDHCP() {
39
+
40
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring MACVTAP networking..."
41
+
42
+ # Create the necessary file structure for /dev/vhost-net
43
+ if [ ! -c /dev/vhost-net ]; then
44
+ if mknod /dev/vhost-net c 10 238; then
45
+ chmod 660 /dev/vhost-net
46
+ fi
47
+ fi
48
+
49
+ # Create a macvtap network for the VM guest
50
+ { msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1); rc=$?; } || :
51
+
52
+ case "$msg" in
53
+ "RTNETLINK answers: File exists"* )
54
+ while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do
55
+ info "Waiting for macvtap interface to become available.."
56
+ sleep 5
57
+ done ;;
58
+ "RTNETLINK answers: Invalid argument"* )
59
+ error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'."
60
+ return 1 ;;
61
+ "RTNETLINK answers: Operation not permitted"* )
62
+ error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set."
63
+ return 1 ;;
64
+ *)
65
+ [ -n "$msg" ] && echo "$msg" >&2
66
+ if (( rc != 0 )); then
67
+ error "Cannot create macvtap interface."
68
+ return 1
69
+ fi ;;
70
+ esac
71
+
72
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
73
+ if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
74
+ warn "Failed to set MTU size to $MTU."
75
+ fi
76
+ fi
77
+
78
+ while ! ip link set "$VM_NET_TAP" up; do
79
+ info "Waiting for MAC address $VM_NET_MAC to become available..."
80
+ info "If you cloned this machine, please delete the '$PROCESS.mac' file to generate a different MAC address."
81
+ sleep 2
82
+ done
83
+
84
+ local TAP_NR TAP_PATH MAJOR MINOR
85
+ TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
86
+ TAP_PATH="/dev/tap${TAP_NR}"
87
+
88
+ # Create dev file (there is no udev in container: need to be done manually)
89
+ IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
90
+ (( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
91
+
92
+ [[ ! -e "$TAP_PATH" && -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
93
+
94
+ if [[ ! -e "$TAP_PATH" ]]; then
95
+ { mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
96
+ (( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
97
+ fi
98
+
99
+ { exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
100
+
101
+ if (( rc != 0 )); then
102
+ error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
103
+ fi
104
+
105
+ { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
106
+
107
+ if (( rc != 0 )); then
108
+ error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
109
+ fi
110
+
111
+ NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
112
+
113
+ return 0
114
+ }
115
+
116
+ configureDNS() {
117
+
118
+ local if="$1"
119
+ local ip="$2"
120
+ local mac="$3"
121
+ local host="$4"
122
+ local mask="$5"
123
+ local gateway="$6"
124
+
125
+ [[ "${DNSMASQ_DISABLE:-}" == [Yy1]* ]] && return 0
126
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Starting dnsmasq daemon..."
127
+
128
+ local log="/var/log/dnsmasq.log"
129
+ rm -f "$log"
130
+
131
+ case "${NETWORK,,}" in
132
+ "tap" | "tun" | "tuntap" | "y" )
133
+
134
+ # Create lease file for faster resolve
135
+ echo "0 $mac $ip $host 01:$mac" > /var/lib/misc/dnsmasq.leases
136
+ chmod 644 /var/lib/misc/dnsmasq.leases
137
+
138
+ # dnsmasq configuration:
139
+ DNSMASQ_OPTS+=" --dhcp-authoritative"
140
+
141
+ # Set DHCP range and host
142
+ DNSMASQ_OPTS+=" --dhcp-range=$ip,$ip"
143
+ DNSMASQ_OPTS+=" --dhcp-host=$mac,,$ip,$host,infinite"
144
+
145
+ # Set DNS server and gateway
146
+ DNSMASQ_OPTS+=" --dhcp-option=option:netmask,$mask"
147
+ DNSMASQ_OPTS+=" --dhcp-option=option:router,$gateway"
148
+ DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,$gateway"
149
+
150
+ esac
151
+
152
+ # Set interfaces
153
+ DNSMASQ_OPTS+=" --interface=$if"
154
+ DNSMASQ_OPTS+=" --bind-interfaces"
155
+
156
+ # Add DNS entry for container
157
+ DNSMASQ_OPTS+=" --address=/host.lan/$gateway"
158
+
159
+ # Set local dns resolver to dnsmasq when needed
160
+ [ -f /etc/resolv.dnsmasq ] && DNSMASQ_OPTS+=" --resolv-file=/etc/resolv.dnsmasq"
161
+
162
+ # Enable logging to file
163
+ DNSMASQ_OPTS+=" --log-facility=$log"
164
+
165
+ DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
166
+ [[ "$DEBUG" == [Yy1]* ]] && printf "Dnsmasq arguments:\n\n%s\n\n" "${DNSMASQ_OPTS// -/$'\n-'}"
167
+
168
+ if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
169
+
170
+ local msg="Failed to start Dnsmasq, reason: $?"
171
+ [ -f "$log" ] && cat "$log"
172
+ error "$msg"
173
+
174
+ return 1
175
+ fi
176
+
177
+ if [[ "$DNSMASQ_DEBUG" == [Yy1]* ]]; then
178
+ tail -fn +0 "$log" --pid=$$ &
179
+ fi
180
+
181
+ return 0
182
+ }
183
+
184
+ compat() {
185
+
186
+ local gateway="$1"
187
+ local interface="$2"
188
+ local samba="20.20.20.1"
189
+
190
+ [[ "$samba" == "$gateway" ]] && return 0
191
+ [[ "${BOOT_MODE:-}" != "windows"* ]] && return 0
192
+
193
+ if [[ "$interface" != "${interface:0:8}" ]]; then
194
+ error "Bridge name too long!" && return 1
195
+ fi
196
+
197
+ # Backwards compatibility with old installations
198
+ if ip address add dev "$interface" "$samba/24" label "$interface:compat" 2>/dev/null; then
199
+ SAMBA_INTERFACE="$samba"
200
+ else
201
+ msg=$(ip address add dev "$interface" "$samba/24" label "$interface:compat" 2>&1)
202
+ if [[ "${msg,,}" != *"address already assigned"* && "$PODMAN" != [Yy1]* ]]; then
203
+ echo "$msg" >&2
204
+ warn "failed to configure IP alias for backwards compatibility. $ADD_ERR --cap-add NET_ADMIN"
205
+ fi
206
+ fi
207
+
208
+ return 0
209
+ }
210
+
211
+ getHostPorts() {
212
+
213
+ local list=""
214
+
215
+ if [[ "${DISPLAY,,}" == "web" ]]; then
216
+ list+="$WSS_PORT,"
217
+ fi
218
+
219
+ if [[ "${DISPLAY,,}" == "vnc" || "${DISPLAY,,}" == "web" ]]; then
220
+ list+="$VNC_PORT,"
221
+ fi
222
+
223
+ list+="$MON_PORT,"
224
+
225
+ if [[ "${WEB:-}" != [Nn]* ]]; then
226
+ list+="$WEB_PORT,"
227
+ list+="$WSD_PORT,"
228
+ fi
229
+
230
+ list+="${HOST_PORTS// /},"
231
+
232
+ # Remove duplicates
233
+ list=$(echo "${list//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g')
234
+
235
+ echo "$list"
236
+ return 0
237
+ }
238
+
239
+ getUserPorts() {
240
+
241
+ local ssh="22"
242
+ [[ "${BOOT_MODE:-}" == "windows"* ]] && ssh="3389"
243
+
244
+ local list="$ssh,"
245
+ list+="${USER_PORTS// /},"
246
+
247
+ local exclude
248
+ exclude=$(getHostPorts)
249
+
250
+ local ports=""
251
+ local userport=""
252
+ local hostport=""
253
+
254
+ for userport in ${list//,/ }; do
255
+
256
+ local num="${userport///tcp}"
257
+ num="${num///udp}"
258
+
259
+ for hostport in ${exclude//,/ }; do
260
+
261
+ local port="${hostport///tcp}"
262
+ port="${port///udp}"
263
+
264
+ if [[ "$num" == "$port" ]]; then
265
+ num=""
266
+ if [[ "$port" != "$WEB_PORT" ]]; then
267
+ warn "Could not assign port $port to \"USER_PORTS\" because it is already in \"HOST_PORTS\"!"
268
+ fi
269
+ fi
270
+
271
+ done
272
+
273
+ [ -n "$num" ] && ports+="$userport,"
274
+
275
+ done
276
+
277
+ # Remove duplicates
278
+ ports=$(echo "${ports//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g')
279
+
280
+ echo "$ports"
281
+ return 0
282
+ }
283
+
284
+ getSlirp() {
285
+
286
+ local args=""
287
+ local list=""
288
+
289
+ list=$(getUserPorts)
290
+
291
+ for port in ${list//,/ }; do
292
+
293
+ local proto="tcp"
294
+ local num="${port%/tcp}"
295
+ [ -z "$num" ] && continue
296
+
297
+ if [[ "$port" == *"/udp" ]]; then
298
+ proto="udp"
299
+ num="${port%/udp}"
300
+ elif [[ "$port" != *"/tcp" ]]; then
301
+ args+="hostfwd=$proto::$num-$VM_NET_IP:$num,"
302
+ proto="udp"
303
+ num="${port%/udp}"
304
+ fi
305
+
306
+ args+="hostfwd=$proto::$num-$VM_NET_IP:$num,"
307
+ done
308
+
309
+ args=$(echo "$args" | sed 's/,*$//g')
310
+
311
+ echo "${args%?}"
312
+ return 0
313
+ }
314
+
315
+ configureSlirp() {
316
+
317
+ NETWORK="slirp"
318
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring slirp networking..."
319
+
320
+ local ip="$IP"
321
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
322
+ local base="${ip%.*}."
323
+ [ "${ip/$base/}" -lt "4" ] && ip="${ip%.*}.4"
324
+ local gateway="${ip%.*}.1"
325
+
326
+ # Backwards compatibility
327
+ ! compat "$gateway" "$VM_NET_DEV" && exit 24
328
+
329
+ local ipv6=""
330
+ [ -n "$IP6" ] && ipv6="ipv6=on,"
331
+
332
+ NET_OPTS="-netdev user,id=hostnet0,ipv4=on,host=$gateway,net=${gateway%.*}.0/24,dhcpstart=$ip,${ipv6}hostname=$VM_NET_HOST"
333
+
334
+ local forward=""
335
+ forward=$(getSlirp)
336
+ [ -n "$forward" ] && NET_OPTS+=",$forward"
337
+
338
+ if [[ "${DNSMASQ_DISABLE:-}" != [Yy1]* ]]; then
339
+ [ ! -f /etc/resolv.dnsmasq ] && cp /etc/resolv.conf /etc/resolv.dnsmasq
340
+ configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
341
+ echo -e "nameserver 127.0.0.1\nsearch .\noptions ndots:0" >/etc/resolv.conf
342
+ fi
343
+
344
+ VM_NET_IP="$ip"
345
+ return 0
346
+ }
347
+
348
+ configurePasst() {
349
+
350
+ NETWORK="passt"
351
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring user-mode networking..."
352
+
353
+ local log="/var/log/passt.log"
354
+ rm -f "$log"
355
+
356
+ local pid="/var/run/dnsmasq.pid"
357
+ [ -s "$pid" ] && pKill "$(<"$pid")"
358
+
359
+ local ip="$IP"
360
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
361
+
362
+ local gateway=""
363
+ if [[ "$ip" != *".1" ]]; then
364
+ gateway="${ip%.*}.1"
365
+ else
366
+ gateway="${ip%.*}.2"
367
+ fi
368
+
369
+ # Backwards compatibility
370
+ ! compat "$gateway" "$VM_NET_DEV" && exit 24
371
+
372
+ # passt configuration:
373
+ [ -z "$IP6" ] && PASST_OPTS+=" -4"
374
+
375
+ PASST_OPTS+=" -a $ip"
376
+ PASST_OPTS+=" -g $gateway"
377
+ PASST_OPTS+=" -n $VM_NET_MASK"
378
+ [ -n "$PASST_MTU" ] && PASST_OPTS+=" -m $PASST_MTU"
379
+
380
+ local forward=""
381
+ forward=$(getUserPorts)
382
+ forward="${forward///tcp}"
383
+ forward="${forward///udp}"
384
+
385
+ if [ -n "$forward" ]; then
386
+ forward="%${VM_NET_DEV}/$forward"
387
+ PASST_OPTS+=" -t $forward"
388
+ PASST_OPTS+=" -u $forward"
389
+ fi
390
+
391
+ PASST_OPTS+=" -H $VM_NET_HOST"
392
+ PASST_OPTS+=" -M $GATEWAY_MAC"
393
+ PASST_OPTS+=" -P /var/run/passt.pid"
394
+ PASST_OPTS+=" -l $log"
395
+ PASST_OPTS+=" -q"
396
+
397
+ local uid gid
398
+ uid=$(id -u)
399
+ gid=$(id -g)
400
+ PASST_OPTS+=" --runas $uid:$gid"
401
+
402
+ if [[ "${DNSMASQ_DISABLE:-}" != [Yy1]* ]]; then
403
+ [ ! -f /etc/resolv.dnsmasq ] && cp /etc/resolv.conf /etc/resolv.dnsmasq
404
+ echo -e "nameserver 127.0.0.1\nsearch .\noptions ndots:0" >/etc/resolv.conf
405
+ fi
406
+
407
+ PASST_OPTS=$(echo "$PASST_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
408
+ [[ "$DEBUG" == [Yy1]* ]] && printf "Passt arguments:\n\n%s\n\n" "${PASST_OPTS// -/$'\n-'}"
409
+
410
+ if ! $PASST ${PASST_OPTS:+ $PASST_OPTS} >/dev/null 2>&1; then
411
+
412
+ rm -f "$log"
413
+ PASST_OPTS="${PASST_OPTS/ -q/}"
414
+ { $PASST ${PASST_OPTS:+ $PASST_OPTS}; rc=$?; } || :
415
+
416
+ if (( rc != 0 )); then
417
+ [ -f "$log" ] && cat "$log"
418
+ warn "failed to start passt ($rc), falling back to slirp networking!"
419
+ configureSlirp && return 0 || return 1
420
+ fi
421
+
422
+ fi
423
+
424
+ if [[ "$PASST_DEBUG" == [Yy1]* ]]; then
425
+ tail -fn +0 "$log" --pid=$$ &
426
+ else
427
+ if [[ "$DEBUG" == [Yy1]* ]]; then
428
+ [ -f "$log" ] && cat "$log" && echo ""
429
+ fi
430
+ fi
431
+
432
+ NET_OPTS="-netdev stream,id=hostnet0,server=off,addr.type=unix,addr.path=/tmp/passt_1.socket"
433
+
434
+ configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
435
+
436
+ VM_NET_IP="$ip"
437
+ return 0
438
+ }
439
+
440
+ configureNAT() {
441
+
442
+ local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
443
+ local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
444
+
445
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring NAT networking..."
446
+
447
+ # Create the necessary file structure for /dev/net/tun
448
+ if [ ! -c /dev/net/tun ]; then
449
+ [[ "$PODMAN" == [Yy1]* ]] && return 1
450
+ [ ! -d /dev/net ] && mkdir -m 755 /dev/net
451
+ if mknod /dev/net/tun c 10 200; then
452
+ chmod 666 /dev/net/tun
453
+ fi
454
+ fi
455
+
456
+ if [ ! -c /dev/net/tun ]; then
457
+ warn "$tuntap" && return 1
458
+ fi
459
+
460
+ # Check port forwarding flag
461
+ if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
462
+ { sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1; rc=$?; } || :
463
+ if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
464
+ warn "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1"
465
+ return 1
466
+ fi
467
+ fi
468
+
469
+ local ip base
470
+ base=$(echo "$IP" | sed -r 's/([^.]*.){2}//')
471
+ if [[ "$IP" != "172.30."* ]]; then
472
+ ip="172.30.$base"
473
+ else
474
+ ip="172.31.$base"
475
+ fi
476
+
477
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
478
+
479
+ local gateway=""
480
+ if [[ "$ip" != *".1" ]]; then
481
+ gateway="${ip%.*}.1"
482
+ else
483
+ gateway="${ip%.*}.2"
484
+ fi
485
+
486
+ # Create a bridge with a static IP for the VM guest
487
+ { ip link add dev "$VM_NET_BRIDGE" type bridge ; rc=$?; } || :
488
+
489
+ if (( rc != 0 )); then
490
+ warn "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
491
+ fi
492
+
493
+ if ! ip address add "$gateway/24" broadcast "${ip%.*}.255" dev "$VM_NET_BRIDGE"; then
494
+ warn "failed to add IP address pool!" && return 1
495
+ fi
496
+
497
+ # Backwards compatibility
498
+ ! compat "$gateway" "$VM_NET_BRIDGE" && exit 24
499
+
500
+ while ! ip link set "$VM_NET_BRIDGE" up; do
501
+ info "Waiting for IP address to become available..."
502
+ sleep 2
503
+ done
504
+
505
+ # QEMU Works with taps, set tap to the bridge created
506
+ if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
507
+ warn "$tuntap" && return 1
508
+ fi
509
+
510
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
511
+ if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
512
+ warn "failed to set MTU size to $MTU."
513
+ fi
514
+ fi
515
+
516
+ if ! ip link set dev "$VM_NET_TAP" address "$GATEWAY_MAC"; then
517
+ warn "failed to set gateway MAC address.."
518
+ fi
519
+
520
+ while ! ip link set "$VM_NET_TAP" up promisc on; do
521
+ info "Waiting for TAP to become available..."
522
+ sleep 2
523
+ done
524
+
525
+ if ! ip link set dev "$VM_NET_TAP" master "$VM_NET_BRIDGE"; then
526
+ warn "failed to set master bridge!" && return 1
527
+ fi
528
+
529
+ if grep -wq "nf_tables" /proc/modules; then
530
+ update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null
531
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null
532
+ else
533
+ update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
534
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
535
+ fi
536
+
537
+ exclude=$(getHostPorts)
538
+
539
+ if [ -n "$exclude" ]; then
540
+ if [[ "$exclude" != *","* ]]; then
541
+ exclude=" ! --dport $exclude"
542
+ else
543
+ exclude=" -m multiport ! --dports $exclude"
544
+ fi
545
+ fi
546
+
547
+ if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then
548
+ warn "$tables" && return 1
549
+ fi
550
+
551
+ # shellcheck disable=SC2086
552
+ if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$ip"; then
553
+ warn "failed to configure IP tables!" && return 1
554
+ fi
555
+
556
+ if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$ip"; then
557
+ warn "failed to configure IP tables!" && return 1
558
+ fi
559
+
560
+ if (( KERNEL > 4 )); then
561
+ # Hack for guest VMs complaining about "bad udp checksums in 5 packets"
562
+ iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true
563
+ fi
564
+
565
+ NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
566
+
567
+ if [ -c /dev/vhost-net ]; then
568
+ { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
569
+ (( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
570
+ fi
571
+
572
+ NET_OPTS+=",script=no,downscript=no"
573
+
574
+ configureDNS "$VM_NET_BRIDGE" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
575
+
576
+ VM_NET_IP="$ip"
577
+ return 0
578
+ }
579
+
580
+ closeBridge() {
581
+
582
+ local pid="/var/run/dnsmasq.pid"
583
+ [ -s "$pid" ] && pKill "$(<"$pid")"
584
+ rm -f "$pid"
585
+
586
+ pid="/var/run/passt.pid"
587
+ [ -s "$pid" ] && pKill "$(<"$pid")"
588
+ rm -f "$pid"
589
+
590
+ case "${NETWORK,,}" in
591
+ "user"* | "passt" | "slirp" ) return 0 ;;
592
+ esac
593
+
594
+ ip link set "$VM_NET_TAP" down promisc off &> null || true
595
+ ip link delete "$VM_NET_TAP" &> null || true
596
+
597
+ ip link set "$VM_NET_BRIDGE" down &> null || true
598
+ ip link delete "$VM_NET_BRIDGE" &> null || true
599
+
600
+ return 0
601
+ }
602
+
603
+ closeWeb() {
604
+
605
+ # Shutdown nginx
606
+ nginx -s stop 2> /dev/null
607
+ fWait "nginx"
608
+
609
+ # Shutdown websocket
610
+ local pid="/var/run/websocketd.pid"
611
+ [ -s "$pid" ] && pKill "$(<"$pid")"
612
+ rm -f "$pid"
613
+
614
+ return 0
615
+ }
616
+
617
+ closeNetwork() {
618
+
619
+ if [[ "${WEB:-}" != [Nn]* ]]; then
620
+ closeWeb
621
+ fi
622
+
623
+ [[ "$NETWORK" == [Nn]* ]] && return 0
624
+
625
+ exec 30<&- || true
626
+ exec 40<&- || true
627
+
628
+ if [[ "$DHCP" != [Yy1]* ]]; then
629
+
630
+ closeBridge
631
+ return 0
632
+
633
+ fi
634
+
635
+ ip link set "$VM_NET_TAP" down || true
636
+ ip link delete "$VM_NET_TAP" || true
637
+
638
+ return 0
639
+ }
640
+
641
+ cleanUp() {
642
+
643
+ # Clean up old files
644
+ rm -f /etc/resolv.dnsmasq
645
+ rm -f /var/run/passt.pid
646
+ rm -f /var/run/dnsmasq.pid
647
+
648
+ if [[ -d "/sys/class/net/$VM_NET_TAP" ]]; then
649
+ info "Lingering interface will be removed..."
650
+ ip link delete "$VM_NET_TAP" || true
651
+ fi
652
+
653
+ return 0
654
+ }
655
+
656
+ checkOS() {
657
+
658
+ local kernel
659
+ local os=""
660
+ local if="macvlan"
661
+ kernel=$(uname -a)
662
+
663
+ [[ "${kernel,,}" == *"darwin"* ]] && os="$ENGINE Desktop for macOS"
664
+ [[ "${kernel,,}" == *"microsoft"* ]] && os="$ENGINE Desktop for Windows"
665
+
666
+ if [[ "$DHCP" == [Yy1]* ]]; then
667
+ if="macvtap"
668
+ [[ "${kernel,,}" == *"synology"* ]] && os="Synology Container Manager"
669
+ fi
670
+
671
+ if [ -n "$os" ]; then
672
+ warn "you are using $os which does not support $if, please revert to bridge networking!"
673
+ fi
674
+
675
+ return 0
676
+ }
677
+
678
+ getInfo() {
679
+
680
+ if [ -z "$VM_NET_DEV" ]; then
681
+ # Give Kubernetes priority over the default interface
682
+ [ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
683
+ [ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
684
+ [ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
685
+ [ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
686
+ # Automaticly detect the default network interface
687
+ [ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
688
+ [ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
689
+ fi
690
+
691
+ if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
692
+ error "Network interface '$VM_NET_DEV' does not exist inside the container!"
693
+ error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 26
694
+ fi
695
+
696
+ GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}' | head -n 1)
697
+ { IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/ | head -n 1); rc=$?; } 2>/dev/null || :
698
+
699
+ if (( rc != 0 )) && [[ "$DHCP" != [Yy1]* ]]; then
700
+ error "Could not determine container IP address!" && exit 26
701
+ fi
702
+
703
+ IP6=""
704
+ # shellcheck disable=SC2143
705
+ if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then
706
+ { IP6=$(ip -6 addr show dev "$VM_NET_DEV" scope global up); rc=$?; } 2>/dev/null || :
707
+ (( rc != 0 )) && IP6=""
708
+ [ -n "$IP6" ] && IP6=$(echo "$IP6" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d' | head -n 1)
709
+ fi
710
+
711
+ local result nic bus
712
+ result=$(ethtool -i "$VM_NET_DEV")
713
+ nic=$(grep -m 1 -i 'driver:' <<< "$result" | awk '{print $(2)}')
714
+ bus=$(grep -m 1 -i 'bus-info:' <<< "$result" | awk '{print $(2)}')
715
+
716
+ if [[ "${bus,,}" != "" && "${bus,,}" != "n/a" && "${bus,,}" != "tap" ]]; then
717
+ [[ "$DEBUG" == [Yy1]* ]] && info "Detected BUS: $bus"
718
+ error "This container does not support host mode networking!"
719
+ exit 29
720
+ fi
721
+
722
+ if [[ "$DHCP" == [Yy1]* ]]; then
723
+
724
+ checkOS
725
+
726
+ if [[ "${nic,,}" == "ipvlan" ]]; then
727
+ error "This container does not support IPVLAN networking when DHCP=Y."
728
+ exit 29
729
+ fi
730
+
731
+ if [[ "${nic,,}" != "macvlan" ]]; then
732
+ [[ "$DEBUG" == [Yy1]* ]] && info "Detected NIC: $nic"
733
+ error "The container needs to be in a MACVLAN network when DHCP=Y."
734
+ exit 29
735
+ fi
736
+
737
+ else
738
+
739
+ if [[ "$IP" != "172."* && "$IP" != "10.8"* && "$IP" != "10.9"* ]]; then
740
+ checkOS
741
+ fi
742
+
743
+ fi
744
+
745
+ local mtu=""
746
+
747
+ if [ -f "/sys/class/net/$VM_NET_DEV/mtu" ]; then
748
+ mtu=$(< "/sys/class/net/$VM_NET_DEV/mtu")
749
+ fi
750
+
751
+ [ -z "$MTU" ] && MTU="$mtu"
752
+ [ -z "$MTU" ] && MTU="0"
753
+
754
+ if [[ "${ADAPTER,,}" != "virtio-net-pci" ]]; then
755
+ if [[ "$MTU" != "0" ]] && [ "$MTU" -lt "1500" ]; then
756
+ warn "MTU size is $MTU, but cannot be set for $ADAPTER adapters!" && MTU="0"
757
+ fi
758
+ fi
759
+
760
+ if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
761
+ if [[ "$MTU" != "0" ]] && [ "$MTU" -lt "1500" ]; then
762
+ warn "MTU size is $MTU, but cannot be set for legacy Windows versions!" && MTU="0"
763
+ fi
764
+ fi
765
+
766
+ if [ -z "$MAC" ]; then
767
+ local file="$STORAGE/$PROCESS.mac"
768
+ [ -s "$file" ] && MAC=$(<"$file")
769
+ MAC="${MAC//[![:print:]]/}"
770
+ if [ -z "$MAC" ]; then
771
+ # Generate MAC address based on Docker container ID in hostname
772
+ MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
773
+ echo "${MAC^^}" > "$file"
774
+ ! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
775
+ fi
776
+ fi
777
+
778
+ VM_NET_MAC="${MAC^^}"
779
+ VM_NET_MAC="${VM_NET_MAC//-/:}"
780
+
781
+ if [[ ${#VM_NET_MAC} == 12 ]]; then
782
+ m="$VM_NET_MAC"
783
+ VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
784
+ fi
785
+
786
+ if [[ ${#VM_NET_MAC} != 17 ]]; then
787
+ error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
788
+ fi
789
+
790
+ GATEWAY_MAC=$(echo "$VM_NET_MAC" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
791
+
792
+ if [[ "$PODMAN" == [Yy1]* && "$DHCP" != [Yy1]* ]]; then
793
+ if [ -z "$NETWORK" ] || [[ "${NETWORK^^}" == "Y" ]]; then
794
+ # By default Podman has no permissions for NAT networking
795
+ NETWORK="user"
796
+ fi
797
+ fi
798
+
799
+ if [[ "$DEBUG" == [Yy1]* ]]; then
800
+ line="Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC MTU: $mtu"
801
+ [[ "$MTU" != "0" && "$MTU" != "$mtu" ]] && line+=" ($MTU)"
802
+ info "$line"
803
+ if [ -f /etc/resolv.conf ]; then
804
+ nameservers=$(grep '^nameserver*' /etc/resolv.conf | head -c -1 | sed 's/nameserver //g;' | sed -z 's/\n/, /g')
805
+ [ -n "$nameservers" ] && info "Nameservers: $nameservers"
806
+ fi
807
+ echo
808
+ fi
809
+
810
+ return 0
811
+ }
812
+
813
+ # ######################################
814
+ # Configure Network
815
+ # ######################################
816
+
817
+ if [[ "$NETWORK" == [Nn]* ]]; then
818
+ NET_OPTS=""
819
+ return 0
820
+ fi
821
+
822
+ msg="Initializing network..."
823
+ html "$msg"
824
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
825
+
826
+ getInfo
827
+ cleanUp
828
+
829
+ if [[ "$DHCP" == [Yy1]* ]]; then
830
+
831
+ # Configure for macvtap interface
832
+ configureDHCP || exit 20
833
+
834
+ else
835
+
836
+ case "${NETWORK,,}" in
837
+ "passt" | "slirp" | "user"* ) ;;
838
+ "tap" | "tun" | "tuntap" | "y" )
839
+
840
+ # Configure tap interface
841
+ if ! configureNAT; then
842
+
843
+ closeBridge
844
+ NETWORK="user"
845
+ msg="falling back to user-mode networking!"
846
+ msg="failed to setup NAT networking, $msg"
847
+
848
+ fi ;;
849
+
850
+ esac
851
+
852
+ case "${NETWORK,,}" in
853
+ "tap" | "tun" | "tuntap" | "y" ) ;;
854
+ "passt" | "user"* )
855
+
856
+ # Configure for user-mode networking (passt)
857
+ if ! configurePasst; then
858
+ error "Failed to configure user-mode networking!"
859
+ exit 24
860
+ fi ;;
861
+
862
+ "slirp" )
863
+
864
+ # Configure for user-mode networking (slirp)
865
+ if ! configureSlirp; then
866
+ error "Failed to configure user-mode networking!"
867
+ exit 24
868
+ fi ;;
869
+
870
+ *)
871
+ error "Unrecognized NETWORK value: \"$NETWORK\"" && exit 24 ;;
872
+ esac
873
+
874
+ case "${NETWORK,,}" in
875
+ "passt" | "slirp" )
876
+
877
+ if [ -z "$USER_PORTS" ]; then
878
+ desc="$APP"
879
+ [[ "${desc,,}" == "qemu" ]] && desc="the VM"
880
+ info "Notice: because user-mode networking is active, when you need to forward custom ports to $desc, add them to the \"USER_PORTS\" variable."
881
+ fi ;;
882
+
883
+ esac
884
+
885
+ fi
886
+
887
+ NET_OPTS+=" -device $ADAPTER,id=net0,netdev=hostnet0,romfile=,mac=$VM_NET_MAC"
888
+ [[ "$MTU" != "0" && "$MTU" != "1500" ]] && NET_OPTS+=",host_mtu=$MTU"
889
+
890
+ html "Initialized network successfully..."
891
+ return 0
src/proc.sh ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+
6
+ : "${HV="Y"}"
7
+ : "${VMX:="N"}"
8
+ : "${CPU_FLAGS:=""}"
9
+ : "${CPU_MODEL:=""}"
10
+
11
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring KVM..."
12
+
13
+ vendor=$(lscpu | awk '/Vendor ID/{print $3}')
14
+ flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
15
+
16
+ if [[ "$KVM" != [Nn]* ]]; then
17
+
18
+ CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
19
+ KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
20
+
21
+ if [ -z "$CPU_MODEL" ]; then
22
+ CPU_MODEL="host"
23
+ CPU_FEATURES+=",migratable=no"
24
+ fi
25
+
26
+ if [[ "$VMX" == [Nn]* && "${BOOT_MODE,,}" == "windows"* ]]; then
27
+ # Prevents a crash caused by a certain Windows update
28
+ CPU_FEATURES+=",-vmx"
29
+ fi
30
+
31
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
32
+
33
+ # AMD processor
34
+ if grep -qw "tsc_scale" <<< "$flags"; then
35
+ CPU_FEATURES+=",+invtsc"
36
+ fi
37
+
38
+ if [[ "${BOOT_MODE,,}" == "windows"* ]]; then
39
+ CPU_FEATURES+=",arch_capabilities=off"
40
+ fi
41
+
42
+ else
43
+
44
+ # Intel processor
45
+ vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
46
+
47
+ if grep -qw "tsc_scaling" <<< "$vmx"; then
48
+ CPU_FEATURES+=",+invtsc"
49
+ fi
50
+
51
+ fi
52
+
53
+ if [[ "${BOOT_MODE,,}" == "windows"* && "$HV" != [Nn]* ]]; then
54
+
55
+ HV_FEATURES="hv_passthrough"
56
+
57
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
58
+
59
+ # AMD processor
60
+ if ! grep -qw "avic" <<< "$flags"; then
61
+ HV_FEATURES+=",-hv-avic"
62
+ fi
63
+
64
+ HV_FEATURES+=",-hv-evmcs"
65
+
66
+ else
67
+
68
+ # Intel processor
69
+ if ! grep -qw "apicv" <<< "$vmx"; then
70
+ HV_FEATURES+=",-hv-apicv,-hv-evmcs"
71
+ else
72
+ if [[ "$CPU" == "Intel Atom "* || "$CPU" == "Intel Celeron "* || "$CPU" == "Intel Pentium "* ]]; then
73
+ # Prevent eVMCS version range error on budget CPU's
74
+ HV_FEATURES+=",-hv-evmcs"
75
+ fi
76
+ fi
77
+
78
+ fi
79
+
80
+ [ -n "$CPU_FEATURES" ] && CPU_FEATURES+=","
81
+ CPU_FEATURES+="${HV_FEATURES}"
82
+
83
+ fi
84
+
85
+ else
86
+
87
+ KVM_OPTS=""
88
+ CPU_FEATURES="l3-cache=on,+hypervisor"
89
+
90
+ if [[ "$ARCH" == "amd64" ]]; then
91
+ KVM_OPTS=" -accel tcg,thread=multi"
92
+ fi
93
+
94
+ if [ -z "$CPU_MODEL" ]; then
95
+ if [[ "$ARCH" == "amd64" ]]; then
96
+
97
+ if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
98
+
99
+ CPU_MODEL="max"
100
+ CPU_FEATURES+=",migratable=no"
101
+
102
+ else
103
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
104
+
105
+ # AMD processor
106
+ CPU_MODEL="EPYC"
107
+ CPU_FEATURES+=",svm=off,arch_capabilities=off,-fxsr-opt,-misalignsse,-osvw,-topoext,-nrip-save,-xsavec,check"
108
+
109
+ else
110
+
111
+ # Intel processor
112
+ CPU_MODEL="Skylake-Client-v4"
113
+ CPU_FEATURES+=",vmx=off,-pcid,-tsc-deadline,-invpcid,-spec-ctrl,-xsavec,-xsaves,check"
114
+
115
+ fi
116
+ fi
117
+
118
+ else
119
+
120
+ # Intel processor
121
+ CPU_MODEL="Skylake-Client-v4"
122
+ CPU_FEATURES+=",vmx=off,-pcid,-tsc-deadline,-invpcid,-spec-ctrl,-xsavec,-xsaves,check"
123
+
124
+ fi
125
+ fi
126
+
127
+ fi
128
+
129
+ if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
130
+
131
+ args="${ARGUMENTS} "
132
+ prefix="${args/-cpu host,*/}"
133
+ suffix="${args/*-cpu host,/}"
134
+ param="${suffix%% *}"
135
+ suffix="${suffix#* }"
136
+ args="${prefix}${suffix}"
137
+ ARGUMENTS="${args::-1}"
138
+
139
+ if [ -z "$CPU_FLAGS" ]; then
140
+ CPU_FLAGS="$param"
141
+ else
142
+ CPU_FLAGS+=",$param"
143
+ fi
144
+
145
+ else
146
+
147
+ if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
148
+ ARGUMENTS="${ARGUMENTS//-cpu host/}"
149
+ fi
150
+
151
+ fi
152
+
153
+ if [ -z "$CPU_FLAGS" ]; then
154
+ if [ -z "$CPU_FEATURES" ]; then
155
+ CPU_FLAGS="$CPU_MODEL"
156
+ else
157
+ CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
158
+ fi
159
+ else
160
+ if [ -z "$CPU_FEATURES" ]; then
161
+ CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
162
+ else
163
+ CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
164
+ fi
165
+ fi
166
+
167
+ return 0
src/progress.sh ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ info="/run/shm/msg.html"
5
+
6
+ escape () {
7
+ local s
8
+ s=${1//&/\&amp;}
9
+ s=${s//</\&lt;}
10
+ s=${s//>/\&gt;}
11
+ s=${s//'"'/\&quot;}
12
+ printf -- %s "$s"
13
+ return 0
14
+ }
15
+
16
+ path="$1"
17
+ total="$2"
18
+ body=$(escape "$3")
19
+
20
+ if [[ "$body" == *"..." ]]; then
21
+ body="<p class=\"loading\">${body::-3}</p>"
22
+ fi
23
+
24
+ while true
25
+ do
26
+
27
+ if [ ! -s "$path" ] && [ ! -d "$path" ]; then
28
+ bytes="0"
29
+ else
30
+ bytes=$(du -sb "$path" | cut -f1)
31
+ fi
32
+
33
+ if (( bytes > 4096 )); then
34
+ if [ -z "$total" ] || [[ "$total" == "0" ]] || [ "$bytes" -gt "$total" ]; then
35
+ size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
36
+ else
37
+ size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
38
+ size="$size%"
39
+ fi
40
+ [[ "$size" != "0.0%" ]] && echo "${body//(\[P\])/($size)}"> "$info"
41
+ fi
42
+
43
+ sleep 1 & wait $!
44
+
45
+ done
src/reset.sh ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
5
+ [[ "${TRACE:-}" == [Yy1]* ]] && set -o functrace && trap 'echo "# $BASH_COMMAND" >&2' DEBUG
6
+
7
+ [ ! -f "/run/entry.sh" ] && error "Script must be run inside the container!" && exit 11
8
+ [ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
9
+
10
+ # Docker environment variables
11
+
12
+ : "${KVM:="Y"}" # KVM acceleration
13
+ : "${BOOT:=""}" # Path of ISO file
14
+ : "${DEBUG:="N"}" # Disable debugging
15
+ : "${MACHINE:="q35"}" # Machine selection
16
+ : "${ALLOCATE:=""}" # Preallocate diskspace
17
+ : "${ARGUMENTS:=""}" # Extra QEMU parameters
18
+ : "${CPU_CORES:="2"}" # Amount of CPU cores
19
+ : "${RAM_SIZE:="2G"}" # Maximum RAM amount
20
+ : "${RAM_CHECK:="Y"}" # Check available RAM
21
+ : "${DISK_SIZE:="64G"}" # Initial data disk size
22
+ : "${BOOT_MODE:=""}" # Boot system with UEFI
23
+ : "${BOOT_INDEX:="9"}" # Boot index of CD drive
24
+ : "${STORAGE:="/storage"}" # Storage folder location
25
+
26
+ # Helper variables
27
+
28
+ PODMAN="N"
29
+ ENGINE="Docker"
30
+ PROCESS="${APP,,}"
31
+ PROCESS="${PROCESS// /-}"
32
+
33
+ if [ -f "/run/.containerenv" ]; then
34
+ PODMAN="Y"
35
+ ENGINE="Podman"
36
+ fi
37
+
38
+ echo "❯ Starting $APP for $ENGINE v$(</run/version)..."
39
+ echo "❯ For support visit $SUPPORT"
40
+
41
+ INFO="/run/shm/msg.html"
42
+ PAGE="/run/shm/index.html"
43
+ TEMPLATE="/var/www/index.html"
44
+ FOOTER1="$APP for $ENGINE v$(</run/version)"
45
+ FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
46
+
47
+ CPU=$(cpu)
48
+ SYS=$(uname -r)
49
+ HOST=$(hostname -s)
50
+ KERNEL=$(echo "$SYS" | cut -b 1)
51
+ MINOR=$(echo "$SYS" | cut -d '.' -f2)
52
+ ARCH=$(dpkg --print-architecture)
53
+ CORES=$(grep -c '^processor' /proc/cpuinfo)
54
+
55
+ if ! grep -qi "socket(s)" <<< "$(lscpu)"; then
56
+ SOCKETS=1
57
+ else
58
+ SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $(2)}')
59
+ fi
60
+
61
+ CPU_CORES="${CPU_CORES// /}"
62
+ [[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
63
+ [[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
64
+ [[ "${CPU_CORES,,}" == "0" ]] && CPU_CORES="1"
65
+ [ -n "${CPU_CORES//[0-9 ]}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
66
+
67
+ if [ "$CPU_CORES" -gt "$CORES" ]; then
68
+ warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available, so will be limited to ${CORES}."
69
+ CPU_CORES="$CORES"
70
+ fi
71
+
72
+ # Check system
73
+
74
+ if [ ! -d "/dev/shm" ]; then
75
+ error "Directory /dev/shm not found!" && exit 14
76
+ else
77
+ [ ! -d "/run/shm" ] && ln -s /dev/shm /run/shm
78
+ fi
79
+
80
+ # Check folder
81
+
82
+ if [[ "${STORAGE,,}" != "/storage" ]]; then
83
+ mkdir -p "$STORAGE"
84
+ fi
85
+
86
+ if [ ! -d "$STORAGE" ]; then
87
+ error "Storage folder ($STORAGE) not found!" && exit 13
88
+ fi
89
+
90
+ if [ ! -w "$STORAGE" ]; then
91
+ msg="Storage folder ($STORAGE) is not writeable!"
92
+ msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
93
+ error "$msg" && exit 13
94
+ fi
95
+
96
+ # Read memory
97
+ RAM_SPARE=500000000
98
+ RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
99
+ RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
100
+
101
+ RAM_SIZE="${RAM_SIZE// /}"
102
+ [ -z "$RAM_SIZE" ] && error "RAM_SIZE not specified!" && exit 16
103
+
104
+ if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
105
+
106
+ if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
107
+ [ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
108
+ fi
109
+
110
+ RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
111
+ ! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
112
+ RAM_WANTED=$(numfmt --from=iec "$RAM_SIZE")
113
+ [ "$RAM_WANTED" -lt "136314880 " ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
114
+
115
+ fi
116
+
117
+ # Print system info
118
+ SYS="${SYS/-generic/}"
119
+ FS=$(stat -f -c %T "$STORAGE")
120
+ FS="${FS/UNKNOWN //}"
121
+ FS="${FS/ext2\/ext3/ext4}"
122
+ FS=$(echo "$FS" | sed 's/[)(]//g')
123
+ SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
124
+ SPACE_GB=$(formatBytes "$SPACE" "down")
125
+ AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
126
+ TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
127
+
128
+ echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
129
+ echo
130
+
131
+ # Check compatibilty
132
+
133
+ if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
134
+ DISK_IO="threads"
135
+ DISK_CACHE="writeback"
136
+ fi
137
+
138
+ if [[ "${BOOT_MODE:-}" == "windows"* ]]; then
139
+ if [[ "${FS,,}" == "btrfs" ]]; then
140
+ warn "you are using the BTRFS filesystem for /storage, this might introduce issues with Windows Setup!"
141
+ fi
142
+ fi
143
+
144
+ # Check KVM support
145
+
146
+ if [[ "${PLATFORM,,}" == "x64" ]]; then
147
+ TARGET="amd64"
148
+ else
149
+ TARGET="arm64"
150
+ fi
151
+
152
+ if [[ "$KVM" == [Nn]* ]]; then
153
+ warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
154
+ else
155
+ if [[ "${ARCH,,}" != "$TARGET" ]]; then
156
+ KVM="N"
157
+ warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
158
+ fi
159
+ fi
160
+
161
+ if [[ "$KVM" != [Nn]* ]]; then
162
+
163
+ KVM_ERR=""
164
+
165
+ if [ ! -e /dev/kvm ]; then
166
+ KVM_ERR="(/dev/kvm is missing)"
167
+ else
168
+ if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
169
+ KVM_ERR="(/dev/kvm is unwriteable)"
170
+ else
171
+ if [[ "${PLATFORM,,}" == "x64" ]]; then
172
+ flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
173
+ if ! grep -qw "vmx\|svm" <<< "$flags"; then
174
+ KVM_ERR="(not enabled in BIOS)"
175
+ fi
176
+ fi
177
+ fi
178
+ fi
179
+
180
+ if [ -n "$KVM_ERR" ]; then
181
+ # Do not hard-fail in restricted environments (e.g. Hugging Face Spaces).
182
+ # Fall back to software acceleration (TCG) and continue startup.
183
+ warn "KVM acceleration is not available $KVM_ERR, falling back to software TCG (will be slower)."
184
+ KVM="N"
185
+ # Provide guidance for users running locally or on supported hosts
186
+ if [[ "$OSTYPE" =~ ^darwin ]]; then
187
+ warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
188
+ else
189
+ kernel=$(uname -a)
190
+ case "${kernel,,}" in
191
+ *"microsoft"* )
192
+ warn "If you want KVM on Docker Desktop, bind '/dev/kvm' as a volume in container settings." ;;
193
+ *"synology"* )
194
+ warn "If you want KVM on Synology, enable VMM and bind '/dev/kvm' to the container." ;;
195
+ *)
196
+ warn "See the FAQ for possible causes, or set the environment variable KVM=N to force software acceleration." ;;
197
+ esac
198
+ fi
199
+ fi
200
+
201
+ fi
202
+
203
+ return 0
src/server.sh ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ : "${VNC_PORT:="5900"}" # VNC port
5
+ : "${MON_PORT:="7100"}" # Monitor port
6
+ : "${WEB_PORT:="8006"}" # Webserver port
7
+ : "${WSD_PORT:="8004"}" # Websockets port
8
+ : "${WSS_PORT:="5700"}" # Websockets port
9
+
10
+ if (( VNC_PORT < 5900 )); then
11
+ warn "VNC port cannot be set lower than 5900, ignoring value $VNC_PORT."
12
+ VNC_PORT="5900"
13
+ fi
14
+
15
+ cp -r /var/www/* /run/shm
16
+ rm -f /var/run/websocketd.pid
17
+
18
+ html "Starting $APP for $ENGINE..."
19
+
20
+ if [[ "${WEB:-}" != [Nn]* ]]; then
21
+
22
+ mkdir -p /etc/nginx/sites-enabled
23
+ cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
24
+
25
+ user="admin"
26
+ [ -n "${USER:-}" ] && user="${USER:-}"
27
+
28
+ if [ -n "${PASS:-}" ]; then
29
+
30
+ # Set password
31
+ echo "$user:{PLAIN}${PASS:-}" > /etc/nginx/.htpasswd
32
+
33
+ sed -i "s/auth_basic off/auth_basic \"NoVNC\"/g" /etc/nginx/sites-enabled/web.conf
34
+
35
+ fi
36
+
37
+ sed -i "s/listen 8006 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
38
+ sed -i "s/proxy_pass http:\/\/127.0.0.1:5700\/;/proxy_pass http:\/\/127.0.0.1:$WSS_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
39
+ sed -i "s/proxy_pass http:\/\/127.0.0.1:8004\/;/proxy_pass http:\/\/127.0.0.1:$WSD_PORT\/;/g" /etc/nginx/sites-enabled/web.conf
40
+
41
+ # shellcheck disable=SC2143
42
+ if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then
43
+
44
+ sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
45
+
46
+ fi
47
+
48
+ # Start webserver
49
+ nginx -e stderr
50
+
51
+ # Start websocket server
52
+ websocketd --address 127.0.0.1 --port="$WSD_PORT" /run/socket.sh >/var/log/websocketd.log &
53
+ echo "$!" > /var/run/websocketd.pid
54
+
55
+ fi
56
+
57
+ return 0
src/socket.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ lastmsg=""
5
+ path="/run/shm/msg.html"
6
+
7
+ refresh() {
8
+
9
+ [ ! -f "$path" ] && return 0
10
+ [ ! -s "$path" ] && return 0
11
+
12
+ msg=$(< "$path")
13
+ msg="${msg%$'\n'}"
14
+
15
+ [ -z "$msg" ] && return 0
16
+ [[ "$msg" == "$lastmsg" ]] && return 0
17
+
18
+ lastmsg="$msg"
19
+ echo "s: $msg"
20
+ return 0
21
+ }
22
+
23
+ refresh
24
+
25
+ inotifywait -m "$path" |
26
+ while read -r fp event fn; do
27
+ case "${event,,}" in
28
+ "modify"* ) refresh ;;
29
+ "delete_self" ) echo "c: vnc" ;;
30
+ esac
31
+ done
src/start.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # You can override this hook to execute a script before startup!
5
+
6
+ return 0
src/utils.sh ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Helper functions
5
+
6
+ info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "${1:-}" "\E[0m\n"; }
7
+ error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: ${1:-}" "\E[0m\n" >&2; }
8
+ warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: ${1:-}" "\E[0m\n" >&2; }
9
+
10
+ formatBytes() {
11
+ local result
12
+ result=$(numfmt --to=iec --suffix=B "$1" | sed -r 's/([A-Z])/ \1/' | sed 's/ B/ bytes/g;')
13
+ local unit="${result//[0-9. ]}"
14
+ result="${result//[a-zA-Z ]/}"
15
+ if [[ "${2:-}" == "up" ]]; then
16
+ if [[ "$result" == *"."* ]]; then
17
+ result="${result%%.*}"
18
+ result=$((result+1))
19
+ fi
20
+ else
21
+ if [[ "${2:-}" == "down" ]]; then
22
+ result="${result%%.*}"
23
+ fi
24
+ fi
25
+ echo "$result $unit"
26
+ return 0
27
+ }
28
+
29
+ isAlive() {
30
+ local pid="$1"
31
+
32
+ if kill -0 "$pid" 2>/dev/null; then
33
+ return 0
34
+ fi
35
+
36
+ return 1
37
+ }
38
+
39
+ pKill() {
40
+ local pid="$1"
41
+
42
+ { kill -15 "$pid" || true; } 2>/dev/null
43
+
44
+ while isAlive "$pid"; do
45
+ sleep 0.2
46
+ done
47
+
48
+ return 0
49
+ }
50
+
51
+ fWait() {
52
+ local name="$1"
53
+
54
+ while pgrep -f -l "$name" >/dev/null; do
55
+ sleep 0.2
56
+ done
57
+
58
+ return 0
59
+ }
60
+
61
+ fKill() {
62
+ local name="$1"
63
+
64
+ { pkill -f "$name" || true; } 2>/dev/null
65
+ fWait "$name"
66
+
67
+ return 0
68
+ }
69
+
70
+ setOwner() {
71
+ local file="$1"
72
+ local dir uid gid
73
+
74
+ [ ! -f "$file" ] && return 1
75
+
76
+ dir=$(dirname -- "$file")
77
+ uid=$(stat -c '%u' "$dir")
78
+ gid=$(stat -c '%g' "$dir")
79
+
80
+ ! chown "$uid:$gid" "$file" && return 1
81
+
82
+ return 0
83
+ }
84
+
85
+ makeDir() {
86
+ local path="$1"
87
+ local dir uid gid
88
+
89
+ [ -d "$path" ] && return 0
90
+ ! mkdir -p "$path" && return 1
91
+
92
+ dir=$(dirname -- "$path")
93
+ uid=$(stat -c '%u' "$dir")
94
+ gid=$(stat -c '%g' "$dir")
95
+
96
+ ! chown "$uid:$gid" "$path" && return 1
97
+
98
+ return 0
99
+ }
100
+
101
+ escape () {
102
+ local s
103
+ s=${1//&/\&amp;}
104
+ s=${s//</\&lt;}
105
+ s=${s//>/\&gt;}
106
+ s=${s//'"'/\&quot;}
107
+ printf -- %s "$s"
108
+ return 0
109
+ }
110
+
111
+ html() {
112
+ local title
113
+ local body
114
+ local script
115
+ local footer
116
+
117
+ title=$(escape "$APP")
118
+ title="<title>$title</title>"
119
+ footer=$(escape "$FOOTER1")
120
+
121
+ body=$(escape "$1")
122
+ if [[ "$body" == *"..." ]]; then
123
+ body="<p class=\"loading\">${body/.../}</p>"
124
+ fi
125
+
126
+ [ -n "${2:-}" ] && script="$2" || script=""
127
+
128
+ local HTML
129
+ HTML=$(<"$TEMPLATE")
130
+ HTML="${HTML/\[1\]/$title}"
131
+ HTML="${HTML/\[2\]/$script}"
132
+ HTML="${HTML/\[3\]/$body}"
133
+ HTML="${HTML/\[4\]/$footer}"
134
+ HTML="${HTML/\[5\]/$FOOTER2}"
135
+
136
+ echo "$HTML" > "$PAGE"
137
+ echo "$body" > "$INFO"
138
+
139
+ return 0
140
+ }
141
+
142
+ cpu() {
143
+ local ret
144
+ local cpu=""
145
+
146
+ ret=$(lscpu)
147
+
148
+ if grep -qi "model name" <<< "$ret"; then
149
+ cpu=$(echo "$ret" | grep -m 1 -i 'model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
150
+ fi
151
+
152
+ if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
153
+ cpu=$(echo "$ret" | grep -m 1 -i 'model:' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed 's# @.*##g' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
154
+ fi
155
+
156
+ cpu="${cpu// CPU/}"
157
+ cpu="${cpu// [0-9][0-9][0-9] Core}"
158
+ cpu="${cpu// [0-9][0-9] Core}"
159
+ cpu="${cpu// [0-9] Core}"
160
+ cpu="${cpu//[0-9][0-9]th Gen }"
161
+ cpu="${cpu//[0-9]th Gen }"
162
+ cpu="${cpu// Processor/}"
163
+ cpu="${cpu// Quad core/}"
164
+ cpu="${cpu// Dual core/}"
165
+ cpu="${cpu// Octa core/}"
166
+ cpu="${cpu// Hexa core/}"
167
+ cpu="${cpu// Core TM/ Core}"
168
+ cpu="${cpu// with Radeon Graphics/}"
169
+ cpu="${cpu// with Radeon Vega Graphics/}"
170
+ cpu="${cpu// with Radeon Vega Mobile Gfx/}"
171
+ cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
172
+
173
+ [ -z "${cpu// /}" ] && cpu="Unknown"
174
+
175
+ echo "$cpu"
176
+ return 0
177
+ }
178
+
179
+ hasDisk() {
180
+
181
+ [ -b "/disk" ] && return 0
182
+ [ -b "/disk1" ] && return 0
183
+ [ -b "/dev/disk1" ] && return 0
184
+ [ -b "${DEVICE:-}" ] && return 0
185
+
186
+ [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
187
+ [ -s "$STORAGE/$DISK_NAME.img" ] && return 0
188
+ [ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
189
+
190
+ return 1
191
+ }
192
+
193
+ addPackage() {
194
+ local pkg=$1
195
+ local desc=$2
196
+
197
+ if apt-mark showinstall | grep -qx "$pkg"; then
198
+ return 0
199
+ fi
200
+
201
+ local msg="Installing $desc..."
202
+ info "$msg" && html "$msg"
203
+
204
+ DEBIAN_FRONTEND=noninteractive apt-get -qq update
205
+ DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
206
+
207
+ return 0
208
+ }
209
+
210
+ return 0
web/conf/defaults.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "resize": "scale",
3
+ "reconnect_delay": 3000
4
+ }
web/conf/mandatory.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "reconnect": true,
3
+ "autoconnect": true
4
+ }
web/conf/nginx.conf ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+
3
+ listen 8006 default_server;
4
+
5
+ autoindex on;
6
+ tcp_nodelay on;
7
+ server_tokens off;
8
+ absolute_redirect off;
9
+
10
+ error_log /dev/null;
11
+ access_log /dev/null;
12
+
13
+ auth_basic off;
14
+ auth_basic_user_file /etc/nginx/.htpasswd;
15
+
16
+ include /etc/nginx/mime.types;
17
+
18
+ gzip on;
19
+ gzip_vary on;
20
+ gzip_proxied any;
21
+ gzip_comp_level 5;
22
+ gzip_min_length 500;
23
+ gzip_disable "msie6";
24
+ gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
25
+
26
+ add_header Cache-Control "no-cache";
27
+
28
+ location / {
29
+
30
+ root /run/shm;
31
+
32
+ if ( -f /run/shm/index.html) {
33
+ break;
34
+ }
35
+
36
+ try_files /index.html @vnc;
37
+ }
38
+
39
+ location @vnc {
40
+
41
+ root /usr/share/novnc;
42
+ index vnc.html;
43
+
44
+ }
45
+
46
+ location /status {
47
+
48
+ proxy_http_version 1.1;
49
+
50
+ proxy_set_header Connection 'upgrade';
51
+ proxy_set_header Upgrade $http_upgrade;
52
+
53
+ proxy_buffering off;
54
+ proxy_read_timeout 3600s;
55
+ proxy_send_timeout 3600s;
56
+
57
+ proxy_pass http://127.0.0.1:8004/;
58
+ }
59
+
60
+ location /websockify {
61
+
62
+ proxy_http_version 1.1;
63
+
64
+ proxy_set_header Connection 'upgrade';
65
+ proxy_set_header Upgrade $http_upgrade;
66
+
67
+ proxy_buffering off;
68
+ proxy_read_timeout 3600s;
69
+ proxy_send_timeout 3600s;
70
+
71
+ proxy_pass http://127.0.0.1:5700/;
72
+ }
73
+
74
+ }
web/css/style.css ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ color: white;
3
+ background-color: #125bdb;
4
+ font-smoothing: antialiased;
5
+ -webkit-font-smoothing: antialiased;
6
+ -moz-osx-font-smoothing: grayscale;
7
+ font-family: Verdana, Geneva, sans-serif;
8
+ }
9
+
10
+ #info {
11
+ text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.25);
12
+ }
13
+
14
+ #content {
15
+ text-align: center;
16
+ padding: 20px;
17
+ margin-top: 50px;
18
+ }
19
+
20
+ footer {
21
+ width: 98%;
22
+ position: fixed;
23
+ bottom: 0px;
24
+ height: 40px;
25
+ text-align: center;
26
+ color: #0c8aeb;
27
+ text-shadow: 0 0 1px #0c8aeb;
28
+ }
29
+
30
+ #empty {
31
+ height: 40px;
32
+ /* Same height as footer */
33
+ }
34
+
35
+ a,
36
+ a:hover,
37
+ a:active,
38
+ a:visited {
39
+ color: white;
40
+ }
41
+
42
+ footer a:link,
43
+ footer a:visited,
44
+ footer a:active {
45
+ color: #0c8aeb;
46
+ }
47
+
48
+ footer a:hover {
49
+ color: #73e6ff;
50
+ }
51
+
52
+ .loading:after {
53
+ content: " .";
54
+ animation: dots 1s steps(5, end) infinite;
55
+ }
56
+
57
+ @keyframes dots {
58
+
59
+ 0%,
60
+ 20% {
61
+ color: rgba(0, 0, 0, 0);
62
+ text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
63
+ }
64
+
65
+ 40% {
66
+ color: white;
67
+ text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0), 0.5em 0 0 rgba(0, 0, 0, 0);
68
+ }
69
+
70
+ 60% {
71
+ text-shadow: 0.25em 0 0 white, 0.5em 0 0 rgba(0, 0, 0, 0);
72
+ }
73
+
74
+ 80%,
75
+ 100% {
76
+ text-shadow: 0.25em 0 0 white, 0.5em 0 0 white;
77
+ }
78
+ }
79
+
80
+ .spinner_LWk7 {
81
+ animation: spinner_GWy6 1.2s linear infinite, spinner_BNNO 1.2s linear infinite
82
+ }
83
+
84
+ .spinner_yOMU {
85
+ animation: spinner_GWy6 1.2s linear infinite, spinner_pVqn 1.2s linear infinite;
86
+ animation-delay: .15s
87
+ }
88
+
89
+ .spinner_KS4S {
90
+ animation: spinner_GWy6 1.2s linear infinite, spinner_6uKB 1.2s linear infinite;
91
+ animation-delay: .3s
92
+ }
93
+
94
+ .spinner_zVee {
95
+ animation: spinner_GWy6 1.2s linear infinite, spinner_Qw4x 1.2s linear infinite;
96
+ animation-delay: .45s
97
+ }
98
+
99
+ @keyframes spinner_GWy6 {
100
+
101
+ 0%,
102
+ 50% {
103
+ width: 9px;
104
+ height: 9px
105
+ }
106
+
107
+ 10% {
108
+ width: 11px;
109
+ height: 11px
110
+ }
111
+ }
112
+
113
+ @keyframes spinner_BNNO {
114
+
115
+ 0%,
116
+ 50% {
117
+ x: 1.5px;
118
+ y: 1.5px
119
+ }
120
+
121
+ 10% {
122
+ x: .5px;
123
+ y: .5px
124
+ }
125
+ }
126
+
127
+ @keyframes spinner_pVqn {
128
+
129
+ 0%,
130
+ 50% {
131
+ x: 13.5px;
132
+ y: 1.5px
133
+ }
134
+
135
+ 10% {
136
+ x: 12.5px;
137
+ y: .5px
138
+ }
139
+ }
140
+
141
+ @keyframes spinner_6uKB {
142
+
143
+ 0%,
144
+ 50% {
145
+ x: 13.5px;
146
+ y: 13.5px
147
+ }
148
+
149
+ 10% {
150
+ x: 12.5px;
151
+ y: 12.5px
152
+ }
153
+ }
154
+
155
+ @keyframes spinner_Qw4x {
156
+
157
+ 0%,
158
+ 50% {
159
+ x: 1.5px;
160
+ y: 13.5px
161
+ }
162
+
163
+ 10% {
164
+ x: .5px;
165
+ y: 12.5px
166
+ }
167
+ }
web/img/favicon.svg ADDED
web/img/macos.ffs ADDED
Binary file (64.5 kB). View file
 
web/img/qemu.ffs ADDED
Binary file (61.7 kB). View file
 
web/img/windows.ffs ADDED
Binary file (72.8 kB). View file
 
web/index.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ [1]
6
+ <meta http-equiv="Cache-Control" content="no-cache" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <link rel="stylesheet" type="text/css" href="css/style.css" />
9
+ <link rel="icon" href="img/favicon.svg" type="image/x-icon">
10
+ [2]
11
+ </head>
12
+
13
+ <body>
14
+ <div id="page">
15
+ <div id="content">
16
+ <svg id="spinner" width="64" height="64" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
17
+ <rect class="spinner_LWk7" fill="#0c8aeb" x="1.5" y="1.5" rx="1" width="9" height="9"/>
18
+ <rect class="spinner_yOMU" fill="#0c8aeb" x="13.5" y="1.5" rx="1" width="9" height="9"/>
19
+ <rect class="spinner_KS4S" fill="#0c8aeb" x="13.5" y="13.5" rx="1" width="9" height="9"/>
20
+ <rect class="spinner_zVee" fill="#0c8aeb" x="1.5" y="13.5" rx="1" width="9" height="9"/>
21
+ </svg>
22
+ <h1 id="info">[3]</h1>
23
+ </div>
24
+ <div id="empty">
25
+ </div>
26
+ <footer id="footer">
27
+ [4]<br />
28
+ [5]
29
+ </footer>
30
+ </div>
31
+ <script type="text/javascript" src="js/script.js"></script>
32
+ </body>
33
+
34
+ </html>
web/js/script.js ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var request;
2
+ var interval = 1000;
3
+
4
+ var webSocketFactory = {
5
+ connect: function(url) {
6
+
7
+ var ws = new WebSocket(url);
8
+
9
+ ws.addEventListener("open", e => {
10
+ ws.close();
11
+ window.location.reload();
12
+ });
13
+
14
+ ws.addEventListener("error", e => {
15
+ if (e.target.readyState === 3) {
16
+ setTimeout(() => this.connect(url), 1000);
17
+ }
18
+ });
19
+ }
20
+ };
21
+
22
+ function getInfo() {
23
+
24
+ var url = "msg.html";
25
+
26
+ try {
27
+ if (window.XMLHttpRequest) {
28
+ request = new XMLHttpRequest();
29
+ } else {
30
+ throw "XMLHttpRequest not available!";
31
+ }
32
+
33
+ request.onreadystatechange = processInfo;
34
+ request.open("GET", url, true);
35
+ request.send();
36
+
37
+ } catch (e) {
38
+ setError("Error: " + e.message);
39
+ }
40
+ }
41
+
42
+ function getURL() {
43
+
44
+ var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
45
+ var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
46
+
47
+ return protocol + "//" + window.location.host + path;
48
+ }
49
+
50
+ function redirect() {
51
+
52
+ setInfo("Connecting to VNC", true);
53
+
54
+ var wsUrl = getURL() + "/websockify";
55
+ var webSocket = webSocketFactory.connect(wsUrl);
56
+
57
+ return true;
58
+ }
59
+
60
+ function processInfo() {
61
+ try {
62
+
63
+ if (request.readyState != 4) {
64
+ return true;
65
+ }
66
+
67
+ var msg = request.responseText;
68
+ if (msg == null || msg.length == 0) {
69
+ window.location.reload();
70
+ return false;
71
+ }
72
+
73
+ var notFound = (request.status == 404);
74
+
75
+ if (request.status == 200) {
76
+ if (msg.toLowerCase().indexOf("<html>") !== -1) {
77
+ notFound = true;
78
+ } else {
79
+ setInfo(msg);
80
+ schedule();
81
+ return true;
82
+ }
83
+ }
84
+
85
+ if (notFound) {
86
+ redirect();
87
+ return true;
88
+ }
89
+
90
+ setError("Error: Received statuscode " + request.status);
91
+ return false;
92
+
93
+ } catch (e) {
94
+ setError("Error: " + e.message);
95
+ return false;
96
+ }
97
+ }
98
+
99
+ function extractContent(s) {
100
+ var span = document.createElement('span');
101
+ span.innerHTML = s;
102
+ return span.textContent || span.innerText;
103
+ };
104
+
105
+ function setInfo(msg, loading, error) {
106
+ try {
107
+
108
+ if (msg == null || msg.length == 0) {
109
+ return false;
110
+ }
111
+
112
+ var el = document.getElementById("info");
113
+
114
+ if (el.innerText == msg || el.innerHTML == msg) {
115
+ return true;
116
+ }
117
+
118
+ var spin = document.getElementById("spinner");
119
+
120
+ error = !!error;
121
+ if (!error) {
122
+ spin.style.visibility = 'visible';
123
+ } else {
124
+ spin.style.visibility = 'hidden';
125
+ }
126
+
127
+ var p = "<p class=\"loading\">";
128
+ loading = !!loading;
129
+ if (loading) {
130
+ msg = p + msg + "</p>";
131
+ }
132
+
133
+ if (msg.includes(p)) {
134
+ if (el.innerHTML.includes(p)) {
135
+ el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
136
+ return true;
137
+ }
138
+ }
139
+
140
+ el.innerHTML = msg;
141
+ return true;
142
+
143
+ } catch (e) {
144
+ console.log("Error: " + e.message);
145
+ return false;
146
+ }
147
+ }
148
+
149
+ function setError(text) {
150
+ console.warn(text);
151
+ return setInfo(text, false, true);
152
+ }
153
+
154
+ function schedule() {
155
+ setTimeout(getInfo, interval);
156
+ }
157
+
158
+ function connect() {
159
+
160
+ var wsUrl = getURL() + "/status";
161
+ var ws = new WebSocket(wsUrl);
162
+
163
+ ws.onmessage = function(e) {
164
+
165
+ var pos = e.data.indexOf(":");
166
+ var cmd = e.data.substring(0, pos);
167
+ var msg = e.data.substring(pos + 2);
168
+
169
+ switch (cmd) {
170
+ case "s":
171
+ setInfo(msg);
172
+ break;
173
+ case "c":
174
+ switch (msg) {
175
+ case "vnc":
176
+ redirect();
177
+ break;
178
+ default:
179
+ console.warn("Unknown command: " + msg);
180
+ break;
181
+ }
182
+ break;
183
+ case "e":
184
+ setError(msg);
185
+ break;
186
+ default:
187
+ console.warn("Unknown event: " + cmd);
188
+ break;
189
+ }
190
+ };
191
+
192
+ ws.onclose = function(e) {
193
+ setTimeout(function() {
194
+ connect();
195
+ }, interval);
196
+ };
197
+
198
+ ws.onerror = function(e) {
199
+ ws.close();
200
+ window.location.reload();
201
+ };
202
+ }
203
+
204
+ schedule();
205
+ connect();