Fred808 commited on
Commit
68b7c93
·
verified ·
1 Parent(s): 13fac54

Upload 29 files

Browse files
.devcontainer.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "name": "qemu",
3
+ "service": "qemu",
4
+ "forwardPorts": [8006],
5
+ "dockerComposeFile": "compose.yml"
6
+ }
.dockerignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .dockerignore
2
+ .git
3
+ .github
4
+ .gitignore
5
+ .gitlab-ci.yml
6
+ .gitmodules
7
+ Dockerfile
8
+ Dockerfile.archive
9
+ compose.yml
10
+ compose.yaml
11
+ docker-compose.yml
12
+ docker-compose.yaml
13
+
14
+ *.md
15
+
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+
Dockerfile ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+ ARG DEBCONF_NOWARNINGS="yes"
10
+ ARG DEBIAN_FRONTEND="noninteractive"
11
+ ARG DEBCONF_NONINTERACTIVE_SEEN="true"
12
+
13
+ RUN set -eu && \
14
+ apt-get update && \
15
+ apt-get --no-install-recommends -y install \
16
+ bc \
17
+ jq \
18
+ xxd \
19
+ tini \
20
+ wget \
21
+ 7zip \
22
+ curl \
23
+ ovmf \
24
+ fdisk \
25
+ nginx \
26
+ swtpm \
27
+ procps \
28
+ ethtool \
29
+ iptables \
30
+ iproute2 \
31
+ apt-utils \
32
+ dnsmasq \
33
+ xz-utils \
34
+ net-tools \
35
+ e2fsprogs \
36
+ qemu-utils \
37
+ iputils-ping \
38
+ genisoimage \
39
+ netcat-openbsd \
40
+ ca-certificates \
41
+ qemu-system-x86 && \
42
+ wget "https://github.com/qemus/passt/releases/download/v2025_09_19/passt_2025_09_19_${TARGETARCH}.deb" -O /tmp/passt.deb -q && \
43
+ dpkg -i /tmp/passt.deb && \
44
+ apt-get clean && \
45
+ mkdir -p /etc/qemu && \
46
+ echo "allow br0" > /etc/qemu/bridge.conf && \
47
+ mkdir -p /usr/share/novnc && \
48
+ wget "https://github.com/novnc/noVNC/archive/refs/heads/master.tar.gz" -O /tmp/novnc.tar.gz -q --timeout=10 && \
49
+ tar -xf /tmp/novnc.tar.gz -C /tmp/ && \
50
+ cd "/tmp/noVNC-master" && \
51
+ mv app core vendor package.json ./*.html /usr/share/novnc && \
52
+ unlink /etc/nginx/sites-enabled/default && \
53
+ sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \
54
+ echo "$VERSION_ARG" > /run/version && \
55
+ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
56
+
57
+ COPY --chmod=755 ./src /run/
58
+ COPY --chmod=755 ./web /var/www/
59
+ COPY --chmod=664 ./web/conf/defaults.json /usr/share/novnc
60
+ COPY --chmod=664 ./web/conf/mandatory.json /usr/share/novnc
61
+ COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf
62
+
63
+ ADD --chmod=755 "https://github.com/qemus/fiano/releases/download/v1.2.0/utk_1.2.0_${TARGETARCH}.bin" /run/utk.bin
64
+
65
+ VOLUME /storage
66
+ EXPOSE 22 5900 8006
67
+
68
+
69
+
70
+ # Make the entire /app directory fully writeable for all users
71
+ RUN chmod -R 777 /app
72
+
73
+ # Ensure the app runs as the same user as the Space UI
74
+ RUN useradd -m -u 1000 user
75
+ USER user
76
+
77
+
78
+ ENV BOOT="alpine"
79
+ ENV CPU_CORES="2"
80
+ ENV RAM_SIZE="2G"
81
+ ENV DISK_SIZE="64G"
82
+
83
+ 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
src/boot.sh ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ cp "$OVMF/$ROM" "$DEST.rom"
77
+ if [[ "${LOGO:-}" != [Nn]* ]]; then
78
+ /run/utk.bin "$DEST.rom" replace_ffs LogoDXE "/var/www/img/${PROCESS,,}.ffs" save "$DEST.rom"
79
+ fi
80
+ fi
81
+
82
+ if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then
83
+ [ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45
84
+ cp "$OVMF/$VARS" "$DEST.vars"
85
+ fi
86
+
87
+ if [[ "${BOOT_MODE,,}" == "secure" || "${BOOT_MODE,,}" == "windows_secure" ]]; then
88
+ BOOT_OPTS+=" -global driver=cfi.pflash01,property=secure,value=on"
89
+ fi
90
+
91
+ BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on"
92
+ BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw"
93
+
94
+ ;;
95
+ esac
96
+
97
+ MSRS="/sys/module/kvm/parameters/ignore_msrs"
98
+ if [ -e "$MSRS" ]; then
99
+ result=$(<"$MSRS")
100
+ result="${result//[![:print:]]/}"
101
+ if [[ "$result" == "0" || "${result^^}" == "N" ]]; then
102
+ echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
103
+ fi
104
+ fi
105
+
106
+ CLOCKSOURCE="tsc"
107
+ [[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
108
+ CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
109
+
110
+ if [ ! -f "$CLOCK" ]; then
111
+ warn "file \"$CLOCK\" cannot not found?"
112
+ else
113
+ result=$(<"$CLOCK")
114
+ result="${result//[![:print:]]/}"
115
+ case "${result,,}" in
116
+ "${CLOCKSOURCE,,}" ) ;;
117
+ "kvm-clock" ) info "Nested KVM virtualization detected.." ;;
118
+ "hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
119
+ "hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
120
+ *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
121
+ esac
122
+ fi
123
+
124
+ SM_BIOS=""
125
+ PS="/sys/class/dmi/id/product_serial"
126
+
127
+ if [ -s "$PS" ] && [ -r "$PS" ]; then
128
+
129
+ BIOS_SERIAL=$(<"$PS")
130
+ BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}"
131
+
132
+ if [ -n "$BIOS_SERIAL" ]; then
133
+ SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL"
134
+ fi
135
+
136
+ fi
137
+
138
+ rm -f /var/run/tpm.pid
139
+
140
+ if [[ "$TPM" == [Yy1]* ]]; then
141
+
142
+ 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
143
+ error "Failed to start TPM emulator, reason: $?"
144
+ else
145
+
146
+ for (( i = 1; i < 20; i++ )); do
147
+
148
+ [ -S "/run/swtpm-sock" ] && break
149
+
150
+ if (( i % 10 == 0 )); then
151
+ echo "Waiting for TPM emulator to become available..."
152
+ fi
153
+
154
+ sleep 0.1
155
+
156
+ done
157
+
158
+ if [ ! -S "/run/swtpm-sock" ]; then
159
+ error "TPM socket not found? Disabling TPM module..."
160
+ else
161
+ BOOT_OPTS+=" -chardev socket,id=chrtpm,path=/run/swtpm-sock"
162
+ BOOT_OPTS+=" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
163
+ fi
164
+
165
+ fi
166
+ fi
167
+
168
+ return 0
src/config.sh ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ if [[ "${DISPLAY,,}" == "web" ]]; then
46
+ [ ! -f "$INFO" ] && error "File $INFO not found?!"
47
+ rm -f "$INFO"
48
+ [ ! -f "$PAGE" ] && error "File $PAGE not found?!"
49
+ rm -f "$PAGE"
50
+ else
51
+ if [[ "${DISPLAY,,}" == "vnc" ]]; then
52
+ html "You can now connect to VNC on port $VNC_PORT." "0"
53
+ else
54
+ html "The virtual machine was booted successfully." "0"
55
+ fi
56
+ fi
57
+
58
+ # Check available memory as the very last step
59
+
60
+ if [[ "$RAM_CHECK" != [Nn]* ]]; then
61
+
62
+ RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
63
+ AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
64
+
65
+ if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
66
+ msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of memory available, please set a lower value."
67
+ [[ "${FS,,}" != "zfs" ]] && error "$msg" && exit 17
68
+ info "$msg"
69
+ else
70
+ if (( (RAM_WANTED + (RAM_SPARE * 3)) > RAM_AVAIL )); then
71
+ msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $AVAIL_MEM of memory available, please consider a lower value."
72
+ if [[ "${FS,,}" != "zfs" ]]; then
73
+ warn "$msg"
74
+ else
75
+ info "$msg"
76
+ fi
77
+ fi
78
+ fi
79
+
80
+ fi
81
+
82
+ if [[ "$DEBUG" == [Yy1]* ]]; then
83
+ printf "QEMU arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
84
+ fi
85
+
86
+ return 0
src/define.sh ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ esac
201
+
202
+ case "${ret,,}" in
203
+ "name" )
204
+ echo "$name"
205
+ ;;
206
+ "url" )
207
+
208
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
209
+ if [ -n "$name" ] && [ -z "$url" ]; then
210
+ error "No image for $name available!"
211
+ return 1
212
+ fi
213
+ else
214
+ if [ -n "$name" ] && [ -z "$arm" ]; then
215
+ error "No image for $name is available for ARM64 yet! "
216
+ return 1
217
+ fi
218
+ fi
219
+
220
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
221
+ echo "$url"
222
+ else
223
+ echo "$arm"
224
+ fi ;;
225
+ esac
226
+
227
+ return 0
228
+ }
229
+
230
+ return 0
src/disk.sh ADDED
@@ -0,0 +1,728 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
96
+
97
+ rm -f "$DISK_FILE"
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
+ fi
111
+
112
+ html "Creating a $DISK_DESC image..."
113
+ info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
114
+
115
+ local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
116
+
117
+ case "${DISK_FMT,,}" in
118
+ raw)
119
+
120
+ if isCow "$FS"; then
121
+ if ! touch "$DISK_FILE"; then
122
+ error "$FAIL" && exit 77
123
+ fi
124
+ { chattr +C "$DISK_FILE"; } || :
125
+ fi
126
+
127
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
128
+
129
+ # Create an empty file
130
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
131
+ rm -f "$DISK_FILE"
132
+ error "$FAIL" && exit 77
133
+ fi
134
+
135
+ else
136
+
137
+ # Create an empty file
138
+ if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
139
+ if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
140
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
141
+ rm -f "$DISK_FILE"
142
+ error "$FAIL" && exit 77
143
+ fi
144
+ fi
145
+ fi
146
+
147
+ fi
148
+ ;;
149
+ qcow2)
150
+
151
+ local DISK_PARAM="$DISK_ALLOC"
152
+ isCow "$FS" && DISK_PARAM+=",nocow=on"
153
+ [ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
154
+
155
+ if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
156
+ rm -f "$DISK_FILE"
157
+ error "$FAIL" && exit 70
158
+ fi
159
+ ;;
160
+ esac
161
+
162
+ if isCow "$FS"; then
163
+ FA=$(lsattr "$DISK_FILE")
164
+ if [[ "$FA" != *"C"* ]]; then
165
+ error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
166
+ fi
167
+ fi
168
+
169
+ return 0
170
+ }
171
+
172
+ resizeDisk() {
173
+
174
+ local DISK_FILE=$1
175
+ local DISK_SPACE=$2
176
+ local DISK_DESC=$3
177
+ local DISK_FMT=$4
178
+ local FS=$5
179
+ local CUR_SIZE DATA_SIZE DIR SPACE GB
180
+
181
+ CUR_SIZE=$(getSize "$DISK_FILE")
182
+ DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
183
+ local REQ=$((DATA_SIZE-CUR_SIZE))
184
+ (( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
185
+
186
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
187
+
188
+ # Check free diskspace
189
+ DIR=$(dirname "$DISK_FILE")
190
+ SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
191
+
192
+ if (( REQ > SPACE )); then
193
+ GB=$(formatBytes "$SPACE")
194
+ error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.."
195
+ error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
196
+ fi
197
+ fi
198
+
199
+ GB=$(formatBytes "$CUR_SIZE")
200
+ MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/G/ GB}..."
201
+ info "$MSG" && html "$MSG"
202
+
203
+ local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB} to ${DISK_SPACE/G/ GB} ($DISK_FILE)"
204
+
205
+ case "${DISK_FMT,,}" in
206
+ raw)
207
+
208
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
209
+
210
+ # Resize file by changing its length
211
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
212
+ error "$FAIL" && exit 75
213
+ fi
214
+
215
+ else
216
+
217
+ # Resize file by allocating more space
218
+ if ! fallocate -l "$DATA_SIZE" "$DISK_FILE" &>/dev/null; then
219
+ if ! fallocate -l -x "$DATA_SIZE" "$DISK_FILE"; then
220
+ if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
221
+ error "$FAIL" && exit 75
222
+ fi
223
+ fi
224
+ fi
225
+
226
+ fi
227
+ ;;
228
+ qcow2)
229
+
230
+ if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
231
+ error "$FAIL" && exit 72
232
+ fi
233
+
234
+ ;;
235
+ esac
236
+
237
+ return 0
238
+ }
239
+
240
+ convertDisk() {
241
+
242
+ local SOURCE_FILE=$1
243
+ local SOURCE_FMT=$2
244
+ local DST_FILE=$3
245
+ local DST_FMT=$4
246
+ local DISK_BASE=$5
247
+ local DISK_DESC=$6
248
+ local FS=$7
249
+
250
+ [ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
251
+ [ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79
252
+
253
+ local TMP_FILE="$DISK_BASE.tmp"
254
+ rm -f "$TMP_FILE"
255
+
256
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
257
+
258
+ local DIR CUR_SIZE SPACE GB
259
+
260
+ # Check free diskspace
261
+ DIR=$(dirname "$TMP_FILE")
262
+ CUR_SIZE=$(getSize "$SOURCE_FILE")
263
+ SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
264
+
265
+ if (( CUR_SIZE > SPACE )); then
266
+ GB=$(formatBytes "$SPACE")
267
+ error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
268
+ error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
269
+ fi
270
+ fi
271
+
272
+ local msg="Converting $DISK_DESC to $DST_FMT"
273
+ html "$msg..."
274
+ info "$msg, please wait until completed..."
275
+
276
+ local CONV_FLAGS="-p"
277
+ local DISK_PARAM="$DISK_ALLOC"
278
+ isCow "$FS" && DISK_PARAM+=",nocow=on"
279
+
280
+ if [[ "$DST_FMT" != "raw" ]]; then
281
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
282
+ CONV_FLAGS+=" -c"
283
+ fi
284
+ [ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
285
+ fi
286
+
287
+ # shellcheck disable=SC2086
288
+ if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
289
+ rm -f "$TMP_FILE"
290
+ error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79
291
+ fi
292
+
293
+ if [[ "$DST_FMT" == "raw" ]]; then
294
+ if [[ "$ALLOCATE" != [Nn]* ]]; then
295
+ # Work around qemu-img bug
296
+ CUR_SIZE=$(stat -c%s "$TMP_FILE")
297
+ if ! fallocate -l "$CUR_SIZE" "$TMP_FILE" &>/dev/null; then
298
+ if ! fallocate -l -x "$CUR_SIZE" "$TMP_FILE"; then
299
+ error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
300
+ fi
301
+ fi
302
+ fi
303
+ fi
304
+
305
+ rm -f "$SOURCE_FILE"
306
+ mv "$TMP_FILE" "$DST_FILE"
307
+
308
+ if isCow "$FS"; then
309
+ FA=$(lsattr "$DST_FILE")
310
+ if [[ "$FA" != *"C"* ]]; then
311
+ error "Failed to disable COW for $DISK_DESC image $DST_FILE on ${FS^^} filesystem (returned $FA)"
312
+ fi
313
+ fi
314
+
315
+ msg="Conversion of $DISK_DESC"
316
+ html "$msg completed..."
317
+ info "$msg to $DST_FMT completed successfully!"
318
+
319
+ return 0
320
+ }
321
+
322
+ checkFS () {
323
+
324
+ local FS=$1
325
+ local DISK_FILE=$2
326
+ local DISK_DESC=$3
327
+ local DIR FA
328
+
329
+ DIR=$(dirname "$DISK_FILE")
330
+ [ ! -d "$DIR" ] && return 0
331
+
332
+ if [[ "${FS,,}" == "overlay"* ]]; then
333
+ info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
334
+ fi
335
+
336
+ if [[ "${FS,,}" == "fuse"* ]]; then
337
+ info "Warning: the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
338
+ fi
339
+
340
+ if ! supportsDirect "$FS"; then
341
+ info "Warning: the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
342
+ fi
343
+
344
+ if isCow "$FS"; then
345
+ if [ -f "$DISK_FILE" ]; then
346
+ FA=$(lsattr "$DISK_FILE")
347
+ if [[ "$FA" != *"C"* ]]; then
348
+ info "Warning: COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
349
+ fi
350
+ fi
351
+ fi
352
+
353
+ return 0
354
+ }
355
+
356
+ createDevice () {
357
+
358
+ local DISK_FILE=$1
359
+ local DISK_TYPE=$2
360
+ local DISK_INDEX=$3
361
+ local DISK_ADDRESS=$4
362
+ local DISK_FMT=$5
363
+ local DISK_IO=$6
364
+ local DISK_CACHE=$7
365
+ local DISK_SERIAL=$8
366
+ local DISK_SECTORS=$9
367
+ local DISK_ID="data$DISK_INDEX"
368
+
369
+ local index=""
370
+ [ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
371
+ local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
372
+
373
+ case "${DISK_TYPE,,}" in
374
+ "none" ) ;;
375
+ "auto" )
376
+ echo "$result"
377
+ ;;
378
+ "usb" )
379
+ result+=",if=none \
380
+ -device usb-storage,drive=${DISK_ID}${index}${DISK_SERIAL}${DISK_SECTORS}"
381
+ echo "$result"
382
+ ;;
383
+ "nvme" )
384
+ result+=",if=none \
385
+ -device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}${DISK_SERIAL}${DISK_SECTORS}"
386
+ echo "$result"
387
+ ;;
388
+ "ide" | "sata" )
389
+ result+=",if=none \
390
+ -device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
391
+ -device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}${DISK_SERIAL}${DISK_SECTORS}"
392
+ echo "$result"
393
+ ;;
394
+ "blk" | "virtio-blk" )
395
+ result+=",if=none \
396
+ -device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}${DISK_SERIAL}${DISK_SECTORS}"
397
+ echo "$result"
398
+ ;;
399
+ "scsi" | "virtio-scsi" )
400
+ result+=",if=none \
401
+ -device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
402
+ -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}"
403
+ echo "$result"
404
+ ;;
405
+ esac
406
+
407
+ return 0
408
+ }
409
+
410
+ addMedia () {
411
+
412
+ local DISK_FILE=$1
413
+ local DISK_TYPE=$2
414
+ local DISK_INDEX=$3
415
+ local DISK_ADDRESS=$4
416
+
417
+ local index=""
418
+ local DISK_ID="cdrom$DISK_INDEX"
419
+ [ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
420
+ local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=raw,cache=unsafe,readonly=on,media=cdrom"
421
+
422
+ case "${DISK_TYPE,,}" in
423
+ "none" ) ;;
424
+ "auto" )
425
+ echo "$result"
426
+ ;;
427
+ "usb" )
428
+ result+=",if=none \
429
+ -device usb-storage,drive=${DISK_ID}${index},removable=on"
430
+ echo "$result"
431
+ ;;
432
+ "nvme" )
433
+ result+=",if=none \
434
+ -device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}"
435
+ echo "$result"
436
+ ;;
437
+ "ide" | "sata" )
438
+ result+=",if=none \
439
+ -device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
440
+ -device ide-cd,drive=${DISK_ID},bus=ahci${DISK_INDEX}.0${index}"
441
+ echo "$result"
442
+ ;;
443
+ "blk" | "virtio-blk" )
444
+ result+=",if=none \
445
+ -device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}"
446
+ echo "$result"
447
+ ;;
448
+ "scsi" | "virtio-scsi" )
449
+ result+=",if=none \
450
+ -device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
451
+ -device scsi-cd,drive=${DISK_ID},bus=${DISK_ID}b.0${index}"
452
+ echo "$result"
453
+ ;;
454
+ esac
455
+
456
+ return 0
457
+ }
458
+
459
+ addDisk () {
460
+
461
+ local DISK_BASE=$1
462
+ local DISK_TYPE=$2
463
+ local DISK_DESC=$3
464
+ local DISK_SPACE=$4
465
+ local DISK_INDEX=$5
466
+ local DISK_ADDRESS=$6
467
+ local DISK_FMT=$7
468
+ local DISK_IO=$8
469
+ local DISK_CACHE=$9
470
+ local DISK_EXT DIR SPACE DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE
471
+
472
+ DISK_EXT=$(fmt2ext "$DISK_FMT")
473
+ local DISK_FILE="$DISK_BASE.$DISK_EXT"
474
+
475
+ DIR=$(dirname "$DISK_FILE")
476
+ [ ! -d "$DIR" ] && return 0
477
+
478
+ SPACE="${DISK_SPACE// /}"
479
+ [ -z "$SPACE" ] && SPACE="64G"
480
+ [ -z "${SPACE//[0-9. ]}" ] && SPACE="${SPACE}G"
481
+ SPACE=$(echo "${SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
482
+
483
+ if ! numfmt --from=iec "$SPACE" &>/dev/null; then
484
+ error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
485
+ fi
486
+
487
+ DATA_SIZE=$(numfmt --from=iec "$SPACE")
488
+
489
+ if (( DATA_SIZE < 104857600 )); then
490
+ error "Please increase ${DISK_DESC^^}_SIZE to at least 100 MB." && exit 73
491
+ fi
492
+
493
+ FS=$(stat -f -c %T "$DIR")
494
+ checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
495
+
496
+ if ! supportsDirect "$FS"; then
497
+ DISK_IO="threads"
498
+ DISK_CACHE="writeback"
499
+ fi
500
+
501
+ if ! [ -s "$DISK_FILE" ] ; then
502
+
503
+ if [[ "${DISK_FMT,,}" != "raw" ]]; then
504
+ PREV_FMT="raw"
505
+ else
506
+ PREV_FMT="qcow2"
507
+ fi
508
+
509
+ PREV_EXT=$(fmt2ext "$PREV_FMT")
510
+
511
+ if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
512
+ convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
513
+ fi
514
+ fi
515
+
516
+ if [ -s "$DISK_FILE" ]; then
517
+
518
+ CUR_SIZE=$(getSize "$DISK_FILE")
519
+
520
+ if (( DATA_SIZE > CUR_SIZE )); then
521
+ resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
522
+ fi
523
+
524
+ else
525
+
526
+ createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
527
+
528
+ fi
529
+
530
+ DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" "" "")
531
+
532
+ return 0
533
+ }
534
+
535
+ addDevice () {
536
+
537
+ local DISK_DEV=$1
538
+ local DISK_TYPE=$2
539
+ local DISK_INDEX=$3
540
+ local DISK_ADDRESS=$4
541
+
542
+ [ -z "$DISK_DEV" ] && return 0
543
+ [ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
544
+
545
+ local sectors=""
546
+ local result logical physical
547
+ result=$(fdisk -l "$DISK_DEV" | grep -m 1 -o "(logical/physical): .*" | cut -c 21-)
548
+ logical="${result%% *}"
549
+ physical=$(echo "$result" | grep -m 1 -o "/ .*" | cut -c 3-)
550
+ physical="${physical%% *}"
551
+
552
+ if [ -n "$physical" ]; then
553
+ if [[ "$physical" != "512" ]]; then
554
+ sectors=",logical_block_size=$logical,physical_block_size=$physical"
555
+ fi
556
+ else
557
+ warn "Failed to determine the sector size for $DISK_DEV"
558
+ fi
559
+
560
+ DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE" "" "$sectors")
561
+
562
+ return 0
563
+ }
564
+
565
+ msg="Initializing disks..."
566
+ html "$msg"
567
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
568
+
569
+ [ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
570
+ [ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
571
+ [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
572
+
573
+ case "${DISK_TYPE,,}" in
574
+ "ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
575
+ * ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
576
+ esac
577
+
578
+ if [[ "${PLATFORM,,}" != "arm64" ]]; then
579
+ FALLBACK="ide"
580
+ else
581
+ FALLBACK="usb"
582
+ fi
583
+
584
+ [[ "${BOOT_MODE:-}" == "windows_legacy" ]] && FALLBACK="auto"
585
+
586
+ if [ -z "${MEDIA_TYPE:-}" ]; then
587
+ if [[ "${BOOT_MODE:-}" != "windows"* ]]; then
588
+ if [[ "${DISK_TYPE,,}" == "blk" ]]; then
589
+ MEDIA_TYPE="$FALLBACK"
590
+ else
591
+ MEDIA_TYPE="$DISK_TYPE"
592
+ fi
593
+ else
594
+ MEDIA_TYPE="$FALLBACK"
595
+ fi
596
+ fi
597
+
598
+ case "${MEDIA_TYPE,,}" in
599
+ "ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
600
+ * ) error "Invalid MEDIA_TYPE specified, value \"$MEDIA_TYPE\" is not recognized!" && exit 80 ;;
601
+ esac
602
+
603
+ if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
604
+ case "${BOOT,,}" in
605
+ *".iso" )
606
+ if [[ "${HYBRID:-}" == [Yy]* ]]; then
607
+ DISK_OPTS+=$(addMedia "$BOOT" "usb" "$BOOT_INDEX" "0x5")
608
+ else
609
+ DISK_OPTS+=$(addMedia "$BOOT" "$MEDIA_TYPE" "$BOOT_INDEX" "0x5")
610
+ fi ;;
611
+ *".img" | *".raw" )
612
+ DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "raw" "$DISK_IO" "$DISK_CACHE" "" "") ;;
613
+ *".qcow2" )
614
+ DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "qcow2" "$DISK_IO" "$DISK_CACHE" "" "") ;;
615
+ * )
616
+ error "Invalid BOOT image specified, extension \".${BOOT/*./}\" is not recognized!" && exit 80 ;;
617
+ esac
618
+ fi
619
+
620
+ DRIVERS="/drivers.iso"
621
+ [ ! -f "$DRIVERS" ] || [ ! -s "$DRIVERS" ] && DRIVERS="$STORAGE/drivers.iso"
622
+
623
+ if [ -f "$DRIVERS" ] && [ -s "$DRIVERS" ]; then
624
+ DISK_OPTS+=$(addMedia "$DRIVERS" "$FALLBACK" "" "0x6")
625
+ fi
626
+
627
+ RESCUE="/start.iso"
628
+ [ ! -f "$RESCUE" ] || [ ! -s "$RESCUE" ] && RESCUE="$STORAGE/start.iso"
629
+
630
+ if [ -f "$RESCUE" ] && [ -s "$RESCUE" ]; then
631
+ DISK_OPTS+=$(addMedia "$RESCUE" "$FALLBACK" "1" "0x6")
632
+ fi
633
+
634
+ DISK1_FILE="$STORAGE/${DISK_NAME}"
635
+ DISK2_FILE="/storage2/${DISK_NAME}2"
636
+ DISK3_FILE="/storage3/${DISK_NAME}3"
637
+ DISK4_FILE="/storage4/${DISK_NAME}4"
638
+ DISK5_FILE="/storage5/${DISK_NAME}5"
639
+ DISK6_FILE="/storage6/${DISK_NAME}6"
640
+
641
+ if [ -z "$DISK_FMT" ]; then
642
+ if [ -f "$DISK1_FILE.qcow2" ]; then
643
+ DISK_FMT="qcow2"
644
+ else
645
+ DISK_FMT="raw"
646
+ fi
647
+ fi
648
+
649
+ if [ -z "$ALLOCATE" ]; then
650
+ ALLOCATE="N"
651
+ fi
652
+
653
+ if [[ "$ALLOCATE" == [Nn]* ]]; then
654
+ DISK_STYLE="growable"
655
+ DISK_ALLOC="preallocation=off"
656
+ else
657
+ DISK_STYLE="preallocated"
658
+ DISK_ALLOC="preallocation=falloc"
659
+ fi
660
+
661
+ : "${DISK2_SIZE:=""}"
662
+ : "${DISK3_SIZE:=""}"
663
+ : "${DISK4_SIZE:=""}"
664
+ : "${DISK5_SIZE:=""}"
665
+ : "${DISK6_SIZE:=""}"
666
+
667
+ : "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
668
+ : "${DEVICE2:=""}"
669
+ : "${DEVICE3:=""}"
670
+ : "${DEVICE4:=""}"
671
+ : "${DEVICE5:=""}"
672
+ : "${DEVICE6:=""}"
673
+
674
+ [ -z "$DEVICE" ] && [ -b "/disk" ] && DEVICE="/disk"
675
+ [ -z "$DEVICE" ] && [ -b "/disk1" ] && DEVICE="/disk1"
676
+ [ -z "$DEVICE2" ] && [ -b "/disk2" ] && DEVICE2="/disk2"
677
+ [ -z "$DEVICE3" ] && [ -b "/disk3" ] && DEVICE3="/disk3"
678
+ [ -z "$DEVICE4" ] && [ -b "/disk4" ] && DEVICE4="/disk4"
679
+ [ -z "$DEVICE5" ] && [ -b "/disk5" ] && DEVICE5="/disk5"
680
+ [ -z "$DEVICE6" ] && [ -b "/disk6" ] && DEVICE6="/disk6"
681
+
682
+ [ -z "$DEVICE" ] && [ -b "/dev/disk1" ] && DEVICE="/dev/disk1"
683
+ [ -z "$DEVICE2" ] && [ -b "/dev/disk2" ] && DEVICE2="/dev/disk2"
684
+ [ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
685
+ [ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
686
+ [ -z "$DEVICE5" ] && [ -b "/dev/disk5" ] && DEVICE4="/dev/disk5"
687
+ [ -z "$DEVICE6" ] && [ -b "/dev/disk6" ] && DEVICE4="/dev/disk6"
688
+
689
+ if [ -n "$DEVICE" ]; then
690
+ addDevice "$DEVICE" "$DISK_TYPE" "3" "0xa" || exit $?
691
+ else
692
+ addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xa" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
693
+ fi
694
+
695
+ if [ -n "$DEVICE2" ]; then
696
+ addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xb" || exit $?
697
+ else
698
+ addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xb" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
699
+ fi
700
+
701
+ if [ -n "$DEVICE3" ]; then
702
+ addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xc" || exit $?
703
+ else
704
+ addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
705
+ fi
706
+
707
+ if [ -n "$DEVICE4" ]; then
708
+ addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xd" || exit $?
709
+ else
710
+ addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
711
+ fi
712
+
713
+ if [ -n "$DEVICE5" ]; then
714
+ addDevice "$DEVICE5" "$DISK_TYPE" "7" "0xe" || exit $?
715
+ else
716
+ addDisk "$DISK5_FILE" "$DISK_TYPE" "disk5" "$DISK5_SIZE" "7" "0xe" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
717
+ fi
718
+
719
+ if [ -n "$DEVICE6" ]; then
720
+ addDevice "$DEVICE6" "$DISK_TYPE" "8" "0xf" || exit $?
721
+ else
722
+ addDisk "$DISK6_FILE" "$DISK_TYPE" "disk6" "$DISK6_SIZE" "8" "0xf" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
723
+ fi
724
+
725
+ DISK_OPTS+=" -object iothread,id=io2"
726
+
727
+ html "Initialized disks successfully..."
728
+ return 0
src/display.sh ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"
src/entry.sh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 # Placeholder
11
+ . utils.sh # Load functions
12
+ . reset.sh # Initialize system
13
+ . define.sh # Define images
14
+ . install.sh # Download image
15
+ . disk.sh # Initialize disks
16
+ . display.sh # Initialize graphics
17
+ . network.sh # Initialize network
18
+ . boot.sh # Configure boot
19
+ . proc.sh # Initialize processor
20
+ . config.sh # Configure arguments
21
+
22
+ trap - ERR
23
+
24
+ version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }')
25
+ info "Booting image${BOOT_DESC} using QEMU v$version..."
26
+
27
+ exec qemu-system-x86_64 ${ARGS:+ $ARGS}
src/install.sh ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
76
+ [ ! -f "$file" ] && return 1
77
+ [ ! -s "$file" ] && return 1
78
+
79
+ case "${file,,}" in
80
+ *".iso" | *".img" | *".raw" | *".qcow2" ) ;;
81
+ * ) return 1 ;;
82
+ esac
83
+
84
+ if [ -n "$BOOT_MODE" ] || [[ "${file,,}" == *".qcow2" ]]; then
85
+ moveFile "$file" && return 0
86
+ return 1
87
+ fi
88
+
89
+ if [[ "${file,,}" == *".iso" ]]; then
90
+
91
+ result=$(head -c 512 "$file" | tail -c 2 | xxd -p)
92
+
93
+ if [[ "$result" != "0000" ]]; then
94
+ [ -z "${HYBRID:-}" ] && HYBRID="Y"
95
+ fi
96
+
97
+ if [[ "${HYBRID:-}" != [Yy]* ]]; then
98
+
99
+ result=$(isoinfo -f -i "$file" 2>/dev/null)
100
+
101
+ if [ -z "$result" ]; then
102
+ error "Failed to read ISO file, invalid format!"
103
+ return 1
104
+ fi
105
+
106
+ result=$(echo "${result^^}" | grep "^/EFI")
107
+ [ -z "$result" ] && BOOT_MODE="legacy"
108
+
109
+ moveFile "$file" && return 0
110
+ return 1
111
+
112
+ fi
113
+ fi
114
+
115
+ result=$(fdisk -l "$file" 2>/dev/null)
116
+ [[ "${result^^}" != *"EFI "* ]] && BOOT_MODE="legacy"
117
+
118
+ moveFile "$file" && return 0
119
+ return 1
120
+ }
121
+
122
+ delay() {
123
+
124
+ local i
125
+ local delay="$1"
126
+ local msg="Retrying failed download in X seconds..."
127
+
128
+ info "${msg/X/$delay}"
129
+
130
+ for i in $(seq "$delay" -1 1); do
131
+ html "${msg/X/$i}"
132
+ sleep 1
133
+ done
134
+
135
+ return 0
136
+ }
137
+
138
+ downloadFile() {
139
+
140
+ local url="$1"
141
+ local base="$2"
142
+ local name="$3"
143
+ local msg rc total size progress
144
+
145
+ local dest="$STORAGE/$base"
146
+
147
+ # Check if running with interactive TTY or redirected to docker log
148
+ if [ -t 1 ]; then
149
+ progress="--progress=bar:noscroll"
150
+ else
151
+ progress="--progress=dot:giga"
152
+ fi
153
+
154
+ if [ -z "$name" ]; then
155
+ msg="Downloading image"
156
+ info "Downloading $base..."
157
+ else
158
+ msg="Downloading $name"
159
+ info "Downloading $name..."
160
+ fi
161
+
162
+ html "$msg..."
163
+
164
+ /run/progress.sh "$dest" "0" "$msg ([P])..." &
165
+
166
+ { wget "$url" -O "$dest" --continue -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || :
167
+
168
+ fKill "progress.sh"
169
+
170
+ if (( rc == 0 )) && [ -f "$dest" ]; then
171
+ total=$(stat -c%s "$dest")
172
+ size=$(formatBytes "$total")
173
+ if [ "$total" -lt 100000 ]; then
174
+ error "Invalid image file: is only $size ?" && return 1
175
+ fi
176
+ html "Download finished successfully..."
177
+ return 0
178
+ fi
179
+
180
+ msg="Failed to download $url"
181
+ (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
182
+ (( rc == 4 )) && error "$msg , network failure!" && return 1
183
+ (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
184
+
185
+ error "$msg , reason: $rc"
186
+ return 1
187
+ }
188
+
189
+ convertImage() {
190
+
191
+ local source_file=$1
192
+ local source_fmt=$2
193
+ local dst_file=$3
194
+ local dst_fmt=$4
195
+ local dir base fs fa space space_gb
196
+ local cur_size cur_gb src_size disk_param
197
+
198
+ [ -f "$dst_file" ] && error "Conversion failed, destination file $dst_file already exists?" && return 1
199
+ [ ! -f "$source_file" ] && error "Conversion failed, source file $source_file does not exists?" && return 1
200
+
201
+ if [[ "${source_fmt,,}" == "${dst_fmt,,}" ]]; then
202
+ mv -f "$source_file" "$dst_file"
203
+ return 0
204
+ fi
205
+
206
+ local tmp_file="$dst_file.tmp"
207
+ dir=$(dirname "$tmp_file")
208
+
209
+ rm -f "$tmp_file"
210
+
211
+ if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
212
+
213
+ # Check free diskspace
214
+ src_size=$(qemu-img info "$source_file" -f "$source_fmt" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/')
215
+ space=$(df --output=avail -B 1 "$dir" | tail -n 1)
216
+
217
+ if (( src_size > space )); then
218
+ space_gb=$(formatBytes "$space")
219
+ error "Not enough free space to convert image in $dir, it has only $space_gb available..." && return 1
220
+ fi
221
+ fi
222
+
223
+ base=$(basename "$source_file")
224
+ info "Converting $base..."
225
+ html "Converting image..."
226
+
227
+ local conv_flags="-p"
228
+
229
+ if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
230
+ disk_param="preallocation=off"
231
+ else
232
+ disk_param="preallocation=falloc"
233
+ fi
234
+
235
+ fs=$(stat -f -c %T "$dir")
236
+ [[ "${fs,,}" == "btrfs" ]] && disk_param+=",nocow=on"
237
+
238
+ if [[ "$dst_fmt" != "raw" ]]; then
239
+ if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
240
+ conv_flags+=" -c"
241
+ fi
242
+ [ -n "${DISK_FLAGS:-}" ] && disk_param+=",$DISK_FLAGS"
243
+ fi
244
+
245
+ # shellcheck disable=SC2086
246
+ if ! qemu-img convert -f "$source_fmt" $conv_flags -o "$disk_param" -O "$dst_fmt" -- "$source_file" "$tmp_file"; then
247
+ rm -f "$tmp_file"
248
+ error "Failed to convert image in $dir, is there enough space available?" && return 1
249
+ fi
250
+
251
+ if [[ "$dst_fmt" == "raw" ]]; then
252
+ if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
253
+ # Work around qemu-img bug
254
+ cur_size=$(stat -c%s "$tmp_file")
255
+ cur_gb=$(formatBytes "$cur_size")
256
+ if ! fallocate -l "$cur_size" "$tmp_file" &>/dev/null; then
257
+ if ! fallocate -l -x "$cur_size" "$tmp_file"; then
258
+ error "Failed to allocate $cur_gb for image!"
259
+ fi
260
+ fi
261
+ fi
262
+ fi
263
+
264
+ rm -f "$source_file"
265
+ mv "$tmp_file" "$dst_file"
266
+
267
+ if [[ "${fs,,}" == "btrfs" ]]; then
268
+ fa=$(lsattr "$dst_file")
269
+ if [[ "$fa" != *"C"* ]]; then
270
+ error "Failed to disable COW for image on ${fs^^} filesystem!"
271
+ fi
272
+ fi
273
+
274
+ html "Conversion completed..."
275
+ return 0
276
+ }
277
+
278
+ findFile() {
279
+
280
+ local dir file
281
+ local base="$1"
282
+ local ext="$2"
283
+ local fname="${base}.${ext}"
284
+
285
+ dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit)
286
+ [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit)
287
+
288
+ if [ -d "$dir" ]; then
289
+ if hasDisk; then
290
+ BOOT="none"
291
+ return 0
292
+ fi
293
+ error "The bind $dir maps to a file that does not exist!" && exit 37
294
+ fi
295
+
296
+ file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit)
297
+ [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit)
298
+
299
+ detectType "$file" && return 0
300
+
301
+ return 1
302
+ }
303
+
304
+ findFile "boot" "img" && return 0
305
+ findFile "boot" "raw" && return 0
306
+ findFile "boot" "iso" && return 0
307
+ findFile "boot" "qcow2" && return 0
308
+ findFile "custom" "iso" && return 0
309
+
310
+ if hasDisk; then
311
+ BOOT="none"
312
+ return 0
313
+ fi
314
+
315
+ if [[ "${BOOT}" == \"*\" || "${BOOT}" == \'*\' ]]; then
316
+ VERSION="${BOOT:1:-1}"
317
+ fi
318
+
319
+ BOOT=$(expr "$BOOT" : "^\ *\(.*[^ ]\)\ *$")
320
+
321
+ if [ -z "$BOOT" ] || [[ "$BOOT" == *"example.com/"* ]]; then
322
+
323
+ BOOT="alpine"
324
+ warn "no value specified for the BOOT variable, defaulting to \"${BOOT}\"."
325
+
326
+ fi
327
+
328
+ folder=$(getFolder "$BOOT")
329
+ STORAGE="$STORAGE/$folder"
330
+
331
+ if [ -d "$STORAGE" ]; then
332
+
333
+ findFile "boot" "img" && return 0
334
+ findFile "boot" "raw" && return 0
335
+ findFile "boot" "iso" && return 0
336
+ findFile "boot" "qcow2" && return 0
337
+ findFile "custom" "iso" && return 0
338
+
339
+ if hasDisk; then
340
+ BOOT="none"
341
+ return 0
342
+ fi
343
+
344
+ fi
345
+
346
+ name=$(getURL "$BOOT" "name") || exit 34
347
+
348
+ if [ -n "$name" ]; then
349
+
350
+ msg="Retrieving latest $name version..."
351
+ info "$msg" && html "$msg..."
352
+
353
+ url=$(getURL "$BOOT" "url") || exit 34
354
+
355
+ [ -n "$url" ] && BOOT="$url"
356
+
357
+ fi
358
+
359
+ if [[ "$BOOT" != *"."* ]]; then
360
+ if [ -z "$BOOT" ]; then
361
+ error "No BOOT value specified!"
362
+ else
363
+ error "Invalid BOOT value specified, option \"$BOOT\" is not recognized!"
364
+ fi
365
+ exit 64
366
+ fi
367
+
368
+ if [[ "${BOOT,,}" != "http"* ]]; then
369
+ error "Invalid BOOT value specified, \"$BOOT\" is not a valid URL!" && exit 64
370
+ fi
371
+
372
+ mkdir -p "$STORAGE"
373
+
374
+ find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete
375
+ find "$STORAGE" -maxdepth 1 -type f \( -iname 'data.*' -or -iname 'qemu.*' \) -delete
376
+
377
+ base=$(getBase "$BOOT")
378
+
379
+ rm -f "$STORAGE/$base"
380
+
381
+ if ! downloadFile "$BOOT" "$base" "$name"; then
382
+ delay 5
383
+ if ! downloadFile "$BOOT" "$base" "$name"; then
384
+ delay 10
385
+ if ! downloadFile "$BOOT" "$base" "$name"; then
386
+ rm -f "$STORAGE/$base" && exit 60
387
+ fi
388
+ fi
389
+ fi
390
+
391
+ case "${base,,}" in
392
+ *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
393
+ info "Extracting $base..."
394
+ html "Extracting image..." ;;
395
+ esac
396
+
397
+ case "${base,,}" in
398
+ *".gz" | *".gzip" )
399
+
400
+ gzip -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
401
+ rm -f "$STORAGE/$base"
402
+ base="${base%.*}"
403
+
404
+ ;;
405
+ *".xz" )
406
+
407
+ xz -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
408
+ rm -f "$STORAGE/$base"
409
+ base="${base%.*}"
410
+
411
+ ;;
412
+ *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
413
+
414
+ tmp="$STORAGE/extract"
415
+ rm -rf "$tmp"
416
+ mkdir -p "$tmp"
417
+ 7z x "$STORAGE/$base" -o"$tmp" > /dev/null
418
+
419
+ rm -f "$STORAGE/$base"
420
+ base="${base%.*}"
421
+
422
+ if [ ! -s "$tmp/$base" ]; then
423
+ rm -rf "$tmp"
424
+ error "Cannot find file \"${base}\" in .${BOOT/*./} archive!" && exit 32
425
+ fi
426
+
427
+ mv "$tmp/$base" "$STORAGE/$base"
428
+ rm -rf "$tmp"
429
+
430
+ ;;
431
+ esac
432
+
433
+ case "${base,,}" in
434
+ *".iso" | *".img" | *".raw" | *".qcow2" )
435
+ detectType "$STORAGE/$base" && return 0
436
+ error "Cannot read file \"${base}\"" && exit 63 ;;
437
+ esac
438
+
439
+ target_ext="img"
440
+ target_fmt="${DISK_FMT:-}"
441
+ [ -z "$target_fmt" ] && target_fmt="raw"
442
+ [[ "$target_fmt" != "raw" ]] && target_ext="qcow2"
443
+
444
+ case "${base,,}" in
445
+ *".vdi" ) source_fmt="vdi" ;;
446
+ *".vhd" ) source_fmt="vpc" ;;
447
+ *".vhdx" ) source_fmt="vpc" ;;
448
+ *".vmdk" ) source_fmt="vmdk" ;;
449
+ * ) error "Unknown file extension, type \".${base/*./}\" is not recognized!" && exit 33 ;;
450
+ esac
451
+
452
+ dst="$STORAGE/${base%.*}.$target_ext"
453
+
454
+ ! convertImage "$STORAGE/$base" "$source_fmt" "$dst" "$target_fmt" && exit 35
455
+
456
+ base=$(basename "$dst")
457
+ detectType "$STORAGE/$base" && return 0
458
+ error "Cannot convert file \"${base}\"" && exit 36
src/network.sh ADDED
@@ -0,0 +1,828 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_OPTS:=""}"
24
+ : "${PASST_DEBUG:=""}"
25
+
26
+ : "${DNSMASQ_OPTS:=""}"
27
+ : "${DNSMASQ_DEBUG:=""}"
28
+ : "${DNSMASQ:="/usr/sbin/dnsmasq"}"
29
+ : "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
30
+
31
+ ADD_ERR="Please add the following setting to your container:"
32
+
33
+ # ######################################
34
+ # Functions
35
+ # ######################################
36
+
37
+ configureDHCP() {
38
+
39
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring MACVTAP networking..."
40
+
41
+ # Create the necessary file structure for /dev/vhost-net
42
+ if [ ! -c /dev/vhost-net ]; then
43
+ if mknod /dev/vhost-net c 10 238; then
44
+ chmod 660 /dev/vhost-net
45
+ fi
46
+ fi
47
+
48
+ # Create a macvtap network for the VM guest
49
+ { msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1); rc=$?; } || :
50
+
51
+ case "$msg" in
52
+ "RTNETLINK answers: File exists"* )
53
+ while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do
54
+ info "Waiting for macvtap interface to become available.."
55
+ sleep 5
56
+ done ;;
57
+ "RTNETLINK answers: Invalid argument"* )
58
+ error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'."
59
+ return 1 ;;
60
+ "RTNETLINK answers: Operation not permitted"* )
61
+ error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set."
62
+ return 1 ;;
63
+ *)
64
+ [ -n "$msg" ] && echo "$msg" >&2
65
+ if (( rc != 0 )); then
66
+ error "Cannot create macvtap interface."
67
+ return 1
68
+ fi ;;
69
+ esac
70
+
71
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
72
+ if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
73
+ warn "Failed to set MTU size to $MTU."
74
+ fi
75
+ fi
76
+
77
+ while ! ip link set "$VM_NET_TAP" up; do
78
+ info "Waiting for MAC address $VM_NET_MAC to become available..."
79
+ info "If you cloned this machine, please delete the '$PROCESS.mac' file to generate a different MAC address."
80
+ sleep 2
81
+ done
82
+
83
+ local TAP_NR TAP_PATH MAJOR MINOR
84
+ TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
85
+ TAP_PATH="/dev/tap${TAP_NR}"
86
+
87
+ # Create dev file (there is no udev in container: need to be done manually)
88
+ IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
89
+ (( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
90
+
91
+ [[ ! -e "$TAP_PATH" && -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
92
+
93
+ if [[ ! -e "$TAP_PATH" ]]; then
94
+ { mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
95
+ (( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
96
+ fi
97
+
98
+ { exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
99
+
100
+ if (( rc != 0 )); then
101
+ error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
102
+ fi
103
+
104
+ { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
105
+
106
+ if (( rc != 0 )); then
107
+ error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
108
+ fi
109
+
110
+ NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
111
+
112
+ return 0
113
+ }
114
+
115
+ configureDNS() {
116
+
117
+ local if="$1"
118
+ local ip="$2"
119
+ local mac="$3"
120
+ local host="$4"
121
+ local mask="$5"
122
+ local gateway="$6"
123
+
124
+ [[ "${DNSMASQ_DISABLE:-}" == [Yy1]* ]] && return 0
125
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Starting dnsmasq daemon..."
126
+
127
+ local log="/var/log/dnsmasq.log"
128
+ rm -f "$log"
129
+
130
+ case "${NETWORK,,}" in
131
+ "nat" | "tap" | "tun" | "tuntap" | "y" )
132
+
133
+ # Create lease file for faster resolve
134
+ echo "0 $mac $ip $host 01:$mac" > /var/lib/misc/dnsmasq.leases
135
+ chmod 644 /var/lib/misc/dnsmasq.leases
136
+
137
+ # dnsmasq configuration:
138
+ DNSMASQ_OPTS+=" --dhcp-authoritative"
139
+
140
+ # Set DHCP range and host
141
+ DNSMASQ_OPTS+=" --dhcp-range=$ip,$ip"
142
+ DNSMASQ_OPTS+=" --dhcp-host=$mac,,$ip,$host,infinite"
143
+
144
+ # Set DNS server and gateway
145
+ DNSMASQ_OPTS+=" --dhcp-option=option:netmask,$mask"
146
+ DNSMASQ_OPTS+=" --dhcp-option=option:router,$gateway"
147
+ DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,$gateway"
148
+
149
+ esac
150
+
151
+ # Set interfaces
152
+ DNSMASQ_OPTS+=" --interface=$if"
153
+ DNSMASQ_OPTS+=" --bind-interfaces"
154
+
155
+ # Add DNS entry for container
156
+ DNSMASQ_OPTS+=" --address=/host.lan/$gateway"
157
+
158
+ # Set local dns resolver to dnsmasq when needed
159
+ [ -f /etc/resolv.dnsmasq ] && DNSMASQ_OPTS+=" --resolv-file=/etc/resolv.dnsmasq"
160
+
161
+ # Enable logging to file
162
+ DNSMASQ_OPTS+=" --log-facility=$log"
163
+
164
+ DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
165
+ [[ "$DEBUG" == [Yy1]* ]] && printf "Dnsmasq arguments:\n\n%s\n\n" "${DNSMASQ_OPTS// -/$'\n-'}"
166
+
167
+ if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
168
+
169
+ local msg="Failed to start Dnsmasq, reason: $?"
170
+ [ -f "$log" ] && cat "$log"
171
+ error "$msg"
172
+
173
+ return 1
174
+ fi
175
+
176
+ if [[ "$DNSMASQ_DEBUG" == [Yy1]* ]]; then
177
+ tail -fn +0 "$log" &
178
+ fi
179
+
180
+ return 0
181
+ }
182
+
183
+ getUserPorts() {
184
+
185
+ local args=""
186
+ local list=$1
187
+ local ssh="22"
188
+
189
+ [[ "${BOOT_MODE:-}" == "windows"* ]] && ssh="3389"
190
+ [ -z "$list" ] && list="$ssh" || list+=",$ssh"
191
+
192
+ list="${list//,/ }"
193
+ list="${list## }"
194
+ list="${list%% }"
195
+
196
+ for port in $list; do
197
+ proto="tcp"
198
+ num="$port"
199
+
200
+ if [[ "$port" == */udp ]]; then
201
+ proto="udp"
202
+ num="${port%/udp}"
203
+ elif [[ "$port" == */tcp ]]; then
204
+ proto="tcp"
205
+ num="${port%/tcp}"
206
+ fi
207
+
208
+ args+="hostfwd=$proto::$num-$VM_NET_IP:$num,"
209
+ done
210
+
211
+ echo "${args%?}"
212
+ return 0
213
+ }
214
+
215
+ getHostPorts() {
216
+
217
+ local list="$1"
218
+ list=$(echo "${list// /}" | sed 's/,*$//g')
219
+
220
+ if [[ "${DISPLAY,,}" == "web" ]]; then
221
+ [ -z "$list" ] && list="$WSS_PORT" || list+=",$WSS_PORT"
222
+ fi
223
+
224
+ if [[ "${DISPLAY,,}" == "vnc" || "${DISPLAY,,}" == "web" ]]; then
225
+ [ -z "$list" ] && list="$VNC_PORT" || list+=",$VNC_PORT"
226
+ fi
227
+
228
+ [ -z "$list" ] && list="$MON_PORT" || list+=",$MON_PORT"
229
+
230
+ if [[ "${WEB:-}" != [Nn]* ]]; then
231
+ [ -z "$list" ] && list="$WEB_PORT" || list+=",$WEB_PORT"
232
+ fi
233
+
234
+ if [[ "${NETWORK,,}" == "passt" ]]; then
235
+
236
+ local DNS_PORT="53"
237
+ local SAMBA_PORT="445"
238
+
239
+ if [[ "${DNSMASQ_DISABLE:-}" != [Yy1]* ]]; then
240
+ [ -z "$list" ] && list="$DNS_PORT" || list+=",$DNS_PORT"
241
+ fi
242
+
243
+ if [[ "${BOOT_MODE:-}" == "windows"* ]]; then
244
+ if [[ "${SAMBA:-}" != [Nn]* ]]; then
245
+ [ -z "$list" ] && list="$SAMBA_PORT" || list+=",$SAMBA_PORT"
246
+ fi
247
+ fi
248
+
249
+ fi
250
+
251
+ echo "$list"
252
+ return 0
253
+ }
254
+
255
+ compat() {
256
+
257
+ local gateway="$1"
258
+ local interface="$2"
259
+ local samba="20.20.20.1"
260
+
261
+ [[ "$samba" == "$gateway" ]] && return 0
262
+ [[ "${BOOT_MODE:-}" != "windows"* ]] && return 0
263
+
264
+ if [[ "$interface" != "${interface:0:8}" ]]; then
265
+ error "Bridge name too long!" && return 1
266
+ fi
267
+
268
+ # Backwards compatibility with old installations
269
+ if ip address add dev "$interface" "$samba/24" label "$interface:compat"; then
270
+ SAMBA_INTERFACE="$samba"
271
+ else
272
+ warn "failed to configure IP alias!"
273
+ fi
274
+
275
+ return 0
276
+ }
277
+
278
+ configureSlirp() {
279
+
280
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring slirp networking..."
281
+
282
+ local ip="$IP"
283
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
284
+ local base="${ip%.*}."
285
+ [ "${ip/$base/}" -lt "4" ] && ip="${ip%.*}.4"
286
+ local gateway="${ip%.*}.1"
287
+
288
+ # Backwards compatibility
289
+ ! compat "$gateway" "$VM_NET_DEV" && exit 24
290
+
291
+ local ipv6=""
292
+ [ -n "$IP6" ] && ipv6="ipv6=on,"
293
+
294
+ NET_OPTS="-netdev user,id=hostnet0,ipv4=on,host=$gateway,net=${gateway%.*}.0/24,dhcpstart=$ip,${ipv6}hostname=$VM_NET_HOST"
295
+
296
+ local forward
297
+ forward=$(getUserPorts "${USER_PORTS:-}")
298
+ [ -n "$forward" ] && NET_OPTS+=",$forward"
299
+
300
+ if [[ "${DNSMASQ_DISABLE:-}" != [Yy1]* ]]; then
301
+ cp /etc/resolv.conf /etc/resolv.dnsmasq
302
+ echo -e "nameserver 127.0.0.1\nsearch .\noptions ndots:0" >/etc/resolv.conf
303
+ configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
304
+ fi
305
+
306
+ VM_NET_IP="$ip"
307
+ return 0
308
+ }
309
+
310
+ configurePasst() {
311
+
312
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring user-mode networking..."
313
+
314
+ local log="/var/log/passt.log"
315
+ rm -f "$log"
316
+
317
+ local pid="/var/run/dnsmasq.pid"
318
+ [ -s "$pid" ] && pKill "$(<"$pid")"
319
+
320
+ local ip="$IP"
321
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
322
+
323
+ local gateway=""
324
+ if [[ "$ip" != *".1" ]]; then
325
+ gateway="${ip%.*}.1"
326
+ else
327
+ gateway="${ip%.*}.2"
328
+ fi
329
+
330
+ # Backwards compatibility
331
+ ! compat "$gateway" "$VM_NET_DEV" && exit 24
332
+
333
+ # passt configuration:
334
+ [ -z "$IP6" ] && PASST_OPTS+=" -4"
335
+
336
+ PASST_OPTS+=" -a $ip"
337
+ PASST_OPTS+=" -g $gateway"
338
+ PASST_OPTS+=" -n $VM_NET_MASK"
339
+
340
+ exclude=$(getHostPorts "$HOST_PORTS")
341
+
342
+ if [ -z "$exclude" ]; then
343
+ exclude="all"
344
+ else
345
+ exclude="~${exclude//,/,~}"
346
+ fi
347
+
348
+ PASST_OPTS+=" -t $exclude"
349
+ PASST_OPTS+=" -u $exclude"
350
+ PASST_OPTS+=" -H $VM_NET_HOST"
351
+ PASST_OPTS+=" -M $GATEWAY_MAC"
352
+ PASST_OPTS+=" -P /var/run/passt.pid"
353
+ PASST_OPTS+=" -l $log"
354
+ PASST_OPTS+=" -q"
355
+
356
+ if [[ "${DNSMASQ_DISABLE:-}" != [Yy1]* ]]; then
357
+ cp /etc/resolv.conf /etc/resolv.dnsmasq
358
+ echo -e "nameserver 127.0.0.1\nsearch .\noptions ndots:0" >/etc/resolv.conf
359
+ fi
360
+
361
+ PASST_OPTS=$(echo "$PASST_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
362
+ [[ "$DEBUG" == [Yy1]* ]] && printf "Passt arguments:\n\n%s\n\n" "${PASST_OPTS// -/$'\n-'}"
363
+
364
+ if ! $PASST ${PASST_OPTS:+ $PASST_OPTS} >/dev/null 2>&1; then
365
+ local msg="Failed to start passt, reason: $?"
366
+ [ -f "$log" ] && cat "$log"
367
+ error "$msg"
368
+ return 1
369
+ fi
370
+
371
+ if [[ "$PASST_DEBUG" == [Yy1]* ]]; then
372
+ tail -fn +0 "$log" &
373
+ else
374
+ if [[ "$DEBUG" == [Yy1]* ]]; then
375
+ [ -f "$log" ] && cat "$log" && echo ""
376
+ fi
377
+ fi
378
+
379
+ NET_OPTS="-netdev stream,id=hostnet0,server=off,addr.type=unix,addr.path=/tmp/passt_1.socket"
380
+
381
+ configureDNS "lo" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
382
+
383
+ VM_NET_IP="$ip"
384
+ return 0
385
+ }
386
+
387
+ configureNAT() {
388
+
389
+ local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
390
+ local tables="the 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
391
+
392
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring NAT networking..."
393
+
394
+ # Create the necessary file structure for /dev/net/tun
395
+ if [ ! -c /dev/net/tun ]; then
396
+ [[ "$PODMAN" == [Yy1]* ]] && return 1
397
+ [ ! -d /dev/net ] && mkdir -m 755 /dev/net
398
+ if mknod /dev/net/tun c 10 200; then
399
+ chmod 666 /dev/net/tun
400
+ fi
401
+ fi
402
+
403
+ if [ ! -c /dev/net/tun ]; then
404
+ warn "$tuntap" && return 1
405
+ fi
406
+
407
+ # Check port forwarding flag
408
+ if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
409
+ { sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1; rc=$?; } || :
410
+ if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
411
+ warn "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1"
412
+ return 1
413
+ fi
414
+ fi
415
+
416
+ local ip base
417
+ base=$(echo "$IP" | sed -r 's/([^.]*.){2}//')
418
+ if [[ "$IP" != "172.30."* ]]; then
419
+ ip="172.30.$base"
420
+ else
421
+ ip="172.31.$base"
422
+ fi
423
+
424
+ [ -n "$VM_NET_IP" ] && ip="$VM_NET_IP"
425
+
426
+ local gateway=""
427
+ if [[ "$ip" != *".1" ]]; then
428
+ gateway="${ip%.*}.1"
429
+ else
430
+ gateway="${ip%.*}.2"
431
+ fi
432
+
433
+ # Create a bridge with a static IP for the VM guest
434
+ { ip link add dev "$VM_NET_BRIDGE" type bridge ; rc=$?; } || :
435
+
436
+ if (( rc != 0 )); then
437
+ warn "failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
438
+ fi
439
+
440
+ if ! ip address add "$gateway/24" broadcast "${ip%.*}.255" dev "$VM_NET_BRIDGE"; then
441
+ warn "failed to add IP address pool!" && return 1
442
+ fi
443
+
444
+ # Backwards compatibility
445
+ ! compat "$gateway" "$VM_NET_BRIDGE" && exit 24
446
+
447
+ while ! ip link set "$VM_NET_BRIDGE" up; do
448
+ info "Waiting for IP address to become available..."
449
+ sleep 2
450
+ done
451
+
452
+ # QEMU Works with taps, set tap to the bridge created
453
+ if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
454
+ warn "$tuntap" && return 1
455
+ fi
456
+
457
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
458
+ if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
459
+ warn "failed to set MTU size to $MTU."
460
+ fi
461
+ fi
462
+
463
+ if ! ip link set dev "$VM_NET_TAP" address "$GATEWAY_MAC"; then
464
+ warn "failed to set gateway MAC address.."
465
+ fi
466
+
467
+ while ! ip link set "$VM_NET_TAP" up promisc on; do
468
+ info "Waiting for TAP to become available..."
469
+ sleep 2
470
+ done
471
+
472
+ if ! ip link set dev "$VM_NET_TAP" master "$VM_NET_BRIDGE"; then
473
+ warn "failed to set master bridge!" && return 1
474
+ fi
475
+
476
+ if grep -wq "nf_tables" /proc/modules; then
477
+ update-alternatives --set iptables /usr/sbin/iptables-nft > /dev/null
478
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-nft > /dev/null
479
+ else
480
+ update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
481
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
482
+ fi
483
+
484
+ exclude=$(getHostPorts "$HOST_PORTS")
485
+
486
+ if [ -n "$exclude" ]; then
487
+ if [[ "$exclude" != *","* ]]; then
488
+ exclude=" ! --dport $exclude"
489
+ else
490
+ exclude=" -m multiport ! --dports $exclude"
491
+ fi
492
+ fi
493
+
494
+ if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then
495
+ warn "$tables" && return 1
496
+ fi
497
+
498
+ # shellcheck disable=SC2086
499
+ if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$ip"; then
500
+ warn "failed to configure IP tables!" && return 1
501
+ fi
502
+
503
+ if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$ip"; then
504
+ warn "failed to configure IP tables!" && return 1
505
+ fi
506
+
507
+ if (( KERNEL > 4 )); then
508
+ # Hack for guest VMs complaining about "bad udp checksums in 5 packets"
509
+ iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true
510
+ fi
511
+
512
+ NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
513
+
514
+ if [ -c /dev/vhost-net ]; then
515
+ { exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
516
+ (( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
517
+ fi
518
+
519
+ NET_OPTS+=",script=no,downscript=no"
520
+
521
+ configureDNS "$VM_NET_BRIDGE" "$ip" "$VM_NET_MAC" "$VM_NET_HOST" "$VM_NET_MASK" "$gateway" || return 1
522
+
523
+ VM_NET_IP="$ip"
524
+ return 0
525
+ }
526
+
527
+ closeBridge() {
528
+
529
+ local pid="/var/run/dnsmasq.pid"
530
+ [ -s "$pid" ] && pKill "$(<"$pid")"
531
+ rm -f "$pid"
532
+
533
+ pid="/var/run/passt.pid"
534
+ [ -s "$pid" ] && pKill "$(<"$pid")"
535
+ rm -f "$pid"
536
+
537
+ case "${NETWORK,,}" in
538
+ "user"* | "passt" | "slirp" ) return 0 ;;
539
+ esac
540
+
541
+ ip link set "$VM_NET_TAP" down promisc off &> null || true
542
+ ip link delete "$VM_NET_TAP" &> null || true
543
+
544
+ ip link set "$VM_NET_BRIDGE" down &> null || true
545
+ ip link delete "$VM_NET_BRIDGE" &> null || true
546
+
547
+ return 0
548
+ }
549
+
550
+ closeNetwork() {
551
+
552
+ if [[ "${WEB:-}" != [Nn]* ]]; then
553
+
554
+ # Shutdown nginx
555
+ nginx -s stop 2> /dev/null
556
+ fWait "nginx"
557
+
558
+ fi
559
+
560
+ [[ "$NETWORK" == [Nn]* ]] && return 0
561
+
562
+ exec 30<&- || true
563
+ exec 40<&- || true
564
+
565
+ if [[ "$DHCP" != [Yy1]* ]]; then
566
+
567
+ closeBridge
568
+ return 0
569
+
570
+ fi
571
+
572
+ ip link set "$VM_NET_TAP" down || true
573
+ ip link delete "$VM_NET_TAP" || true
574
+
575
+ return 0
576
+ }
577
+
578
+ cleanUp() {
579
+
580
+ # Clean up old files
581
+ rm -f /etc/resolv.dnsmasq
582
+ rm -f /var/run/passt.pid
583
+ rm -f /var/run/dnsmasq.pid
584
+
585
+ if [[ -d "/sys/class/net/$VM_NET_TAP" ]]; then
586
+ info "Lingering interface will be removed..."
587
+ ip link delete "$VM_NET_TAP" || true
588
+ fi
589
+
590
+ return 0
591
+ }
592
+
593
+ checkOS() {
594
+
595
+ local kernel
596
+ local os=""
597
+ local if="macvlan"
598
+ kernel=$(uname -a)
599
+
600
+ [[ "${kernel,,}" == *"darwin"* ]] && os="$ENGINE Desktop for macOS"
601
+ [[ "${kernel,,}" == *"microsoft"* ]] && os="$ENGINE Desktop for Windows"
602
+
603
+ if [[ "$DHCP" == [Yy1]* ]]; then
604
+ if="macvtap"
605
+ [[ "${kernel,,}" == *"synology"* ]] && os="Synology Container Manager"
606
+ fi
607
+
608
+ if [ -n "$os" ]; then
609
+ warn "you are using $os which does not support $if, please revert to bridge networking!"
610
+ fi
611
+
612
+ return 0
613
+ }
614
+
615
+ getInfo() {
616
+
617
+ if [ -z "$VM_NET_DEV" ]; then
618
+ # Give Kubernetes priority over the default interface
619
+ [ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
620
+ [ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
621
+ [ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
622
+ [ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
623
+ # Automaticly detect the default network interface
624
+ [ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
625
+ [ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
626
+ fi
627
+
628
+ if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
629
+ error "Network interface '$VM_NET_DEV' does not exist inside the container!"
630
+ error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 26
631
+ fi
632
+
633
+ GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}' | head -n 1)
634
+ IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/ | head -n 1)
635
+ IP6=""
636
+
637
+ # shellcheck disable=SC2143
638
+ if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then
639
+ IP6=$(ip -6 addr show dev "$VM_NET_DEV" scope global up)
640
+ [ -n "$IP6" ] && IP6=$(echo "$IP6" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d' | head -n 1)
641
+ fi
642
+
643
+ local result nic bus
644
+ result=$(ethtool -i "$VM_NET_DEV")
645
+ nic=$(grep -m 1 -i 'driver:' <<< "$result" | awk '{print $(2)}')
646
+ bus=$(grep -m 1 -i 'bus-info:' <<< "$result" | awk '{print $(2)}')
647
+
648
+ if [[ "${bus,,}" != "" && "${bus,,}" != "n/a" && "${bus,,}" != "tap" ]]; then
649
+ [[ "$DEBUG" == [Yy1]* ]] && info "Detected BUS: $bus"
650
+ error "This container does not support host mode networking!"
651
+ exit 29
652
+ fi
653
+
654
+ if [[ "$DHCP" == [Yy1]* ]]; then
655
+
656
+ checkOS
657
+
658
+ if [[ "${nic,,}" == "ipvlan" ]]; then
659
+ error "This container does not support IPVLAN networking when DHCP=Y."
660
+ exit 29
661
+ fi
662
+
663
+ if [[ "${nic,,}" != "macvlan" ]]; then
664
+ [[ "$DEBUG" == [Yy1]* ]] && info "Detected NIC: $nic"
665
+ error "The container needs to be in a MACVLAN network when DHCP=Y."
666
+ exit 29
667
+ fi
668
+
669
+ else
670
+
671
+ if [[ "$IP" != "172."* && "$IP" != "10.8"* && "$IP" != "10.9"* ]]; then
672
+ checkOS
673
+ fi
674
+
675
+ fi
676
+
677
+ local mtu=""
678
+
679
+ if [ -f "/sys/class/net/$VM_NET_DEV/mtu" ]; then
680
+ mtu=$(< "/sys/class/net/$VM_NET_DEV/mtu")
681
+ fi
682
+
683
+ [ -z "$MTU" ] && MTU="$mtu"
684
+ [ -z "$MTU" ] && MTU="0"
685
+
686
+ if [ "$MTU" -gt "1500" ]; then
687
+ [[ "$DEBUG" == [Yy1]* ]] && echo "MTU size is too large: $MTU, ignoring..."
688
+ MTU="0"
689
+ fi
690
+
691
+ if [[ "${ADAPTER,,}" != "virtio-net-pci" ]]; then
692
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
693
+ warn "MTU size is $MTU, but cannot be set for $ADAPTER adapters!" && MTU="0"
694
+ fi
695
+ fi
696
+
697
+ if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
698
+ if [[ "$MTU" != "0" && "$MTU" != "1500" ]]; then
699
+ warn "MTU size is $MTU, but cannot be set for legacy Windows versions!" && MTU="0"
700
+ fi
701
+ fi
702
+
703
+ if [ -z "$MAC" ]; then
704
+ local file="$STORAGE/$PROCESS.mac"
705
+ [ -s "$file" ] && MAC=$(<"$file")
706
+ MAC="${MAC//[![:print:]]/}"
707
+ if [ -z "$MAC" ]; then
708
+ # Generate MAC address based on Docker container ID in hostname
709
+ MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
710
+ echo "${MAC^^}" > "$file"
711
+ fi
712
+ fi
713
+
714
+ VM_NET_MAC="${MAC^^}"
715
+ VM_NET_MAC="${VM_NET_MAC//-/:}"
716
+
717
+ if [[ ${#VM_NET_MAC} == 12 ]]; then
718
+ m="$VM_NET_MAC"
719
+ VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
720
+ fi
721
+
722
+ if [[ ${#VM_NET_MAC} != 17 ]]; then
723
+ error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
724
+ fi
725
+
726
+ GATEWAY_MAC=$(echo "$VM_NET_MAC" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
727
+
728
+ if [[ "$PODMAN" == [Yy1]* && "$DHCP" != [Yy1]* ]]; then
729
+ if [ -z "$NETWORK" ] || [[ "${NETWORK^^}" == "Y" ]]; then
730
+ # By default Podman has no permissions for NAT networking
731
+ NETWORK="user"
732
+ fi
733
+ fi
734
+
735
+ if [[ "$DEBUG" == [Yy1]* ]]; then
736
+ line="Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC MTU: $mtu"
737
+ [[ "$MTU" != "0" && "$MTU" != "$mtu" ]] && line+=" ($MTU)"
738
+ info "$line"
739
+ if [ -f /etc/resolv.conf ]; then
740
+ nameservers=$(grep '^nameserver*' /etc/resolv.conf | head -c -1 | sed 's/nameserver //g;' | sed -z 's/\n/, /g')
741
+ [ -n "$nameservers" ] && info "Nameservers: $nameservers"
742
+ fi
743
+ echo
744
+ fi
745
+
746
+ return 0
747
+ }
748
+
749
+ # ######################################
750
+ # Configure Network
751
+ # ######################################
752
+
753
+ if [[ "$NETWORK" == [Nn]* ]]; then
754
+ NET_OPTS=""
755
+ return 0
756
+ fi
757
+
758
+ msg="Initializing network..."
759
+ html "$msg"
760
+ [[ "$DEBUG" == [Yy1]* ]] && echo "$msg"
761
+
762
+ getInfo
763
+ cleanUp
764
+
765
+ if [[ "$DHCP" == [Yy1]* ]]; then
766
+
767
+ # Configure for macvtap interface
768
+ configureDHCP || exit 20
769
+
770
+ else
771
+
772
+ case "${NETWORK,,}" in
773
+ "user"* | "passt" | "slirp" ) ;;
774
+ "nat" | "tap" | "tun" | "tuntap" | "y" )
775
+
776
+ # Configure tap interface
777
+ if ! configureNAT; then
778
+
779
+ closeBridge
780
+ NETWORK="user"
781
+ msg="falling back to user-mode networking!"
782
+ msg="failed to setup NAT networking, $msg"
783
+
784
+ fi ;;
785
+
786
+ esac
787
+
788
+ if [[ "${NETWORK,,}" == "user"* ]]; then
789
+ if [[ "${ADAPTER,,}" != "rtl8139" ]]; then
790
+ NETWORK="passt"
791
+ else
792
+ NETWORK="slirp"
793
+ fi
794
+ fi
795
+
796
+ case "${NETWORK,,}" in
797
+ "nat" | "tap" | "tun" | "tuntap" | "y" ) ;;
798
+ "passt" )
799
+
800
+ # Configure for user-mode networking (passt)
801
+ if ! configurePasst; then
802
+ error "Failed to configure user-mode networking!"
803
+ exit 24
804
+ fi ;;
805
+
806
+ "slirp" )
807
+
808
+ # Configure for user-mode networking (slirp)
809
+ if ! configureSlirp; then
810
+ error "Failed to configure user-mode networking!"
811
+ exit 24
812
+ fi
813
+
814
+ if [ -z "$USER_PORTS" ]; then
815
+ info "Notice: slirp networking is active, so when you want to expose ports, you will need to map them using this variable: \"USER_PORTS=80,443\"."
816
+ fi ;;
817
+
818
+ *)
819
+ error "Unrecognized NETWORK value: \"$NETWORK\"" && exit 24 ;;
820
+ esac
821
+
822
+ fi
823
+
824
+ NET_OPTS+=" -device $ADAPTER,id=net0,netdev=hostnet0,romfile=,mac=$VM_NET_MAC"
825
+ [[ "$MTU" != "0" && "$MTU" != "1500" ]] && NET_OPTS+=",host_mtu=$MTU"
826
+
827
+ html "Initialized network successfully..."
828
+ return 0
src/proc.sh ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Docker environment variables
5
+
6
+ : "${HV="Y"}"
7
+ : "${KVM:="Y"}"
8
+ : "${VMX:="N"}"
9
+ : "${CPU_FLAGS:=""}"
10
+ : "${CPU_MODEL:=""}"
11
+
12
+ [[ "$DEBUG" == [Yy1]* ]] && echo "Configuring KVM..."
13
+
14
+ if [[ "$KVM" == [Nn]* ]]; then
15
+ warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
16
+ else
17
+ if [[ "${ARCH,,}" != "amd64" ]]; then
18
+ KVM="N"
19
+ warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for x64 instructions, so the machine will run about 10 times slower."
20
+ fi
21
+ fi
22
+
23
+ if [[ "$KVM" != [Nn]* ]]; then
24
+
25
+ KVM_ERR=""
26
+
27
+ if [ ! -e /dev/kvm ]; then
28
+ KVM_ERR="(/dev/kvm is missing)"
29
+ else
30
+ if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
31
+ KVM_ERR="(/dev/kvm is unwriteable)"
32
+ else
33
+ flags=$(sed -ne '/^flags/s/^.*: //p' /proc/cpuinfo)
34
+ if ! grep -qw "vmx\|svm" <<< "$flags"; then
35
+ KVM_ERR="(not enabled in BIOS)"
36
+ fi
37
+ fi
38
+ fi
39
+
40
+ if [ -n "$KVM_ERR" ]; then
41
+ KVM="N"
42
+ if [[ "$OSTYPE" =~ ^darwin ]]; then
43
+ warn "you are using macOS which has no KVM support, so the machine will run about 10 times slower."
44
+ else
45
+ kernel=$(uname -a)
46
+ case "${kernel,,}" in
47
+ *"microsoft"* )
48
+ error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
49
+ *"synology"* )
50
+ error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
51
+ *)
52
+ error "KVM acceleration is not available $KVM_ERR, this will cause the machine to run about 10 times slower."
53
+ error "See the FAQ for possible causes, or disable acceleration by adding the \"KVM=N\" variable (not recommended)." ;;
54
+ esac
55
+ [[ "$DEBUG" != [Yy1]* ]] && exit 88
56
+ fi
57
+ fi
58
+
59
+ fi
60
+
61
+ vendor=$(lscpu | awk '/Vendor ID/{print $3}')
62
+
63
+ if [[ "$KVM" != [Nn]* ]]; then
64
+
65
+ CPU_FEATURES="kvm=on,l3-cache=on,+hypervisor"
66
+ KVM_OPTS=",accel=kvm -enable-kvm -global kvm-pit.lost_tick_policy=discard"
67
+
68
+ if [ -z "$CPU_MODEL" ]; then
69
+ CPU_MODEL="host"
70
+ CPU_FEATURES+=",migratable=no"
71
+ fi
72
+
73
+ if [[ "$VMX" == [Nn]* && "${BOOT_MODE,,}" == "windows"* ]]; then
74
+ # Prevents a crash caused by a certain Windows update
75
+ CPU_FEATURES+=",-vmx"
76
+ fi
77
+
78
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
79
+
80
+ # AMD processor
81
+ if grep -qw "tsc_scale" <<< "$flags"; then
82
+ CPU_FEATURES+=",+invtsc"
83
+ fi
84
+
85
+ if [[ "${BOOT_MODE,,}" == "windows"* ]]; then
86
+ CPU_FEATURES+=",arch_capabilities=off"
87
+ fi
88
+
89
+ else
90
+
91
+ # Intel processor
92
+ vmx=$(sed -ne '/^vmx flags/s/^.*: //p' /proc/cpuinfo)
93
+
94
+ if grep -qw "tsc_scaling" <<< "$vmx"; then
95
+ CPU_FEATURES+=",+invtsc"
96
+ fi
97
+
98
+ fi
99
+
100
+ if [[ "${BOOT_MODE,,}" == "windows"* && "$HV" != [Nn]* ]]; then
101
+
102
+ HV_FEATURES="hv_passthrough"
103
+
104
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
105
+
106
+ # AMD processor
107
+ if ! grep -qw "avic" <<< "$flags"; then
108
+ HV_FEATURES+=",-hv-avic"
109
+ fi
110
+
111
+ HV_FEATURES+=",-hv-evmcs"
112
+
113
+ else
114
+
115
+ # Intel processor
116
+ if ! grep -qw "apicv" <<< "$vmx"; then
117
+ HV_FEATURES+=",-hv-apicv,-hv-evmcs"
118
+ else
119
+ if [[ "$CPU" == "Intel Atom "* || "$CPU" == "Intel Celeron "* || "$CPU" == "Intel Pentium "* ]]; then
120
+ # Prevent eVMCS version range error on budget CPU's
121
+ HV_FEATURES+=",-hv-evmcs"
122
+ fi
123
+ fi
124
+
125
+ fi
126
+
127
+ [ -n "$CPU_FEATURES" ] && CPU_FEATURES+=","
128
+ CPU_FEATURES+="${HV_FEATURES}"
129
+
130
+ fi
131
+
132
+ else
133
+
134
+ KVM_OPTS=""
135
+ CPU_FEATURES="l3-cache=on,+hypervisor"
136
+
137
+ if [[ "$ARCH" == "amd64" ]]; then
138
+ KVM_OPTS=" -accel tcg,thread=multi"
139
+ fi
140
+
141
+ if [ -z "$CPU_MODEL" ]; then
142
+ if [[ "$ARCH" == "amd64" ]]; then
143
+
144
+ if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
145
+
146
+ CPU_MODEL="max"
147
+ CPU_FEATURES+=",migratable=no"
148
+
149
+ else
150
+ if [[ "$vendor" == "AuthenticAMD" ]]; then
151
+
152
+ # AMD processor
153
+ CPU_MODEL="EPYC"
154
+ CPU_FEATURES+=",svm=off,arch_capabilities=off,-fxsr-opt,-misalignsse,-osvw,-topoext,-nrip-save,-xsavec,check"
155
+
156
+ else
157
+
158
+ # Intel processor
159
+ CPU_MODEL="Skylake-Client-v4"
160
+ CPU_FEATURES+=",vmx=off,-pcid,-tsc-deadline,-invpcid,-spec-ctrl,-xsavec,-xsaves,check"
161
+
162
+ fi
163
+ fi
164
+
165
+ else
166
+
167
+ # Intel processor
168
+ CPU_MODEL="Skylake-Client-v4"
169
+ CPU_FEATURES+=",vmx=off,-pcid,-tsc-deadline,-invpcid,-spec-ctrl,-xsavec,-xsaves,check"
170
+
171
+ fi
172
+ fi
173
+
174
+ fi
175
+
176
+ if [[ "$ARGUMENTS" == *"-cpu host,"* ]]; then
177
+
178
+ args="${ARGUMENTS} "
179
+ prefix="${args/-cpu host,*/}"
180
+ suffix="${args/*-cpu host,/}"
181
+ param="${suffix%% *}"
182
+ suffix="${suffix#* }"
183
+ args="${prefix}${suffix}"
184
+ ARGUMENTS="${args::-1}"
185
+
186
+ if [ -z "$CPU_FLAGS" ]; then
187
+ CPU_FLAGS="$param"
188
+ else
189
+ CPU_FLAGS+=",$param"
190
+ fi
191
+
192
+ else
193
+
194
+ if [[ "$ARGUMENTS" == *"-cpu host"* ]]; then
195
+ ARGUMENTS="${ARGUMENTS//-cpu host/}"
196
+ fi
197
+
198
+ fi
199
+
200
+ if [ -z "$CPU_FLAGS" ]; then
201
+ if [ -z "$CPU_FEATURES" ]; then
202
+ CPU_FLAGS="$CPU_MODEL"
203
+ else
204
+ CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES"
205
+ fi
206
+ else
207
+ if [ -z "$CPU_FEATURES" ]; then
208
+ CPU_FLAGS="$CPU_MODEL,$CPU_FLAGS"
209
+ else
210
+ CPU_FLAGS="$CPU_MODEL,$CPU_FEATURES,$CPU_FLAGS"
211
+ fi
212
+ fi
213
+
214
+ return 0
src/progress.sh ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ escape () {
5
+ local s
6
+ s=${1//&/\&amp;}
7
+ s=${s//</\&lt;}
8
+ s=${s//>/\&gt;}
9
+ s=${s//'"'/\&quot;}
10
+ printf -- %s "$s"
11
+ return 0
12
+ }
13
+
14
+ file="$1"
15
+ total="$2"
16
+ body=$(escape "$3")
17
+ info="/run/shm/msg.html"
18
+
19
+ if [[ "$body" == *"..." ]]; then
20
+ body="<p class=\"loading\">${body/.../}</p>"
21
+ fi
22
+
23
+ while true
24
+ do
25
+ if [ -s "$file" ]; then
26
+ bytes=$(du -sb "$file" | cut -f1)
27
+ if (( bytes > 1000 )); then
28
+ if [ -z "$total" ] || [[ "$total" == "0" ]]; then
29
+ size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
30
+ else
31
+ size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
32
+ size="$size%"
33
+ fi
34
+ echo "${body//(\[P\])/($size)}"> "$info"
35
+ fi
36
+ fi
37
+ sleep 1 & wait $!
38
+ done
src/reset.sh ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ : "${BOOT:=""}" # Path of ISO file
13
+ : "${DEBUG:="N"}" # Disable debugging
14
+ : "${MACHINE:="q35"}" # Machine selection
15
+ : "${ALLOCATE:=""}" # Preallocate diskspace
16
+ : "${ARGUMENTS:=""}" # Extra QEMU parameters
17
+ : "${CPU_CORES:="2"}" # Amount of CPU cores
18
+ : "${RAM_SIZE:="2G"}" # Maximum RAM amount
19
+ : "${RAM_CHECK:="Y"}" # Check available RAM
20
+ : "${DISK_SIZE:="64G"}" # Initial data disk size
21
+ : "${BOOT_MODE:=""}" # Boot system with UEFI
22
+ : "${BOOT_INDEX:="9"}" # Boot index of CD drive
23
+ : "${STORAGE:="/storage"}" # Storage folder location
24
+
25
+ # Helper variables
26
+
27
+ PODMAN="N"
28
+ ENGINE="Docker"
29
+ PROCESS="${APP,,}"
30
+ PROCESS="${PROCESS// /-}"
31
+
32
+ if [ -f "/run/.containerenv" ]; then
33
+ PODMAN="Y"
34
+ ENGINE="Podman"
35
+ fi
36
+
37
+ echo "❯ Starting $APP for $ENGINE v$(</run/version)..."
38
+ echo "❯ For support visit $SUPPORT"
39
+
40
+ INFO="/run/shm/msg.html"
41
+ PAGE="/run/shm/index.html"
42
+ TEMPLATE="/var/www/index.html"
43
+ FOOTER1="$APP for $ENGINE v$(</run/version)"
44
+ FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
45
+
46
+ CPU=$(cpu)
47
+ SYS=$(uname -r)
48
+ HOST=$(hostname -s)
49
+ KERNEL=$(echo "$SYS" | cut -b 1)
50
+ MINOR=$(echo "$SYS" | cut -d '.' -f2)
51
+ ARCH=$(dpkg --print-architecture)
52
+ CORES=$(grep -c '^processor' /proc/cpuinfo)
53
+
54
+ if ! grep -qi "socket(s)" <<< "$(lscpu)"; then
55
+ SOCKETS=1
56
+ else
57
+ SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $(2)}')
58
+ fi
59
+
60
+ CPU_CORES="${CPU_CORES// /}"
61
+ [[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
62
+ [ -n "${CPU_CORES//[0-9 ]}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
63
+
64
+ if [ "$CPU_CORES" -gt "$CORES" ]; then
65
+ warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of physical cores, so will be limited to ${CORES}."
66
+ CPU_CORES="$CORES"
67
+ fi
68
+
69
+ # Check system
70
+
71
+ if [ ! -d "/dev/shm" ]; then
72
+ error "Directory /dev/shm not found!" && exit 14
73
+ else
74
+ [ ! -d "/run/shm" ] && ln -s /dev/shm /run/shm
75
+ fi
76
+
77
+ # Check folder
78
+
79
+ if [[ "${COMMIT:-}" == [Yy1]* ]]; then
80
+ STORAGE="/local"
81
+ mkdir -p "$STORAGE"
82
+ fi
83
+
84
+ if [ ! -d "$STORAGE" ]; then
85
+ error "Storage folder ($STORAGE) not found!" && exit 13
86
+ fi
87
+
88
+ if [ ! -w "$STORAGE" ]; then
89
+ error "Storage folder ($STORAGE) is not writeable!" && exit 13
90
+ fi
91
+
92
+ # Read memory
93
+ RAM_SPARE=500000000
94
+ RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
95
+ RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
96
+
97
+ RAM_SIZE="${RAM_SIZE// /}"
98
+ [ -z "$RAM_SIZE" ] && error "RAM_SIZE not specified!" && exit 16
99
+
100
+ if [[ "${RAM_SIZE,,}" == "max" ]]; then
101
+ RAM_WANTED=$(( RAM_AVAIL - RAM_SPARE - RAM_SPARE ))
102
+ RAM_WANTED=$(( RAM_WANTED / 1073741825 ))
103
+ RAM_SIZE="${RAM_WANTED}G"
104
+ fi
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
+ # Print system info
116
+ SYS="${SYS/-generic/}"
117
+ FS=$(stat -f -c %T "$STORAGE")
118
+ FS="${FS/UNKNOWN //}"
119
+ FS="${FS/ext2\/ext3/ext4}"
120
+ FS=$(echo "$FS" | sed 's/[)(]//g')
121
+ SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
122
+ SPACE_GB=$(formatBytes "$SPACE" "down")
123
+ AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
124
+ TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
125
+
126
+ echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
127
+ echo
128
+
129
+ # Check compatibilty
130
+
131
+ if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
132
+ DISK_IO="threads"
133
+ DISK_CACHE="writeback"
134
+ fi
135
+
136
+ if [[ "${BOOT_MODE:-}" == "windows"* ]]; then
137
+ if [[ "${FS,,}" == "btrfs" ]]; then
138
+ warn "you are using the BTRFS filesystem for /storage, this might introduce issues with Windows Setup!"
139
+ fi
140
+ fi
141
+
142
+ # Check available memory
143
+
144
+ if [[ "$RAM_CHECK" != [Nn]* ]] && (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
145
+ AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
146
+ msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $AVAIL_MEM of memory available, please set a lower value."
147
+ [[ "${FS,,}" != "zfs" ]] && error "$msg" && exit 17
148
+ info "$msg"
149
+ fi
150
+
151
+ addPackage() {
152
+ local pkg=$1
153
+ local desc=$2
154
+
155
+ if apt-mark showinstall | grep -qx "$pkg"; then
156
+ return 0
157
+ fi
158
+
159
+ MSG="Installing $desc..."
160
+ info "$MSG" && html "$MSG"
161
+
162
+ DEBIAN_FRONTEND=noninteractive apt-get -qq update
163
+ DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
164
+
165
+ return 0
166
+ }
167
+
168
+ : "${VNC_PORT:="5900"}" # VNC port
169
+ : "${MON_PORT:="7100"}" # Monitor port
170
+ : "${WEB_PORT:="8006"}" # Webserver port
171
+ : "${WSS_PORT:="5700"}" # Websockets port
172
+
173
+ if (( VNC_PORT < 5900 )); then
174
+ warn "VNC port cannot be set lower than 5900, ignoring value $VNC_PORT."
175
+ VNC_PORT="5900"
176
+ fi
177
+
178
+ cp -r /var/www/* /run/shm
179
+ html "Starting $APP for $ENGINE..."
180
+
181
+ if [[ "${WEB:-}" != [Nn]* ]]; then
182
+
183
+ mkdir -p /etc/nginx/sites-enabled
184
+ cp /etc/nginx/default.conf /etc/nginx/sites-enabled/web.conf
185
+
186
+ user="admin"
187
+ [ -n "${USER:-}" ] && user="${USER:-}"
188
+
189
+ if [ -n "${PASS:-}" ]; then
190
+
191
+ # Set password
192
+ echo "$user:{PLAIN}${PASS:-}" > /etc/nginx/.htpasswd
193
+
194
+ sed -i "s/auth_basic off/auth_basic \"NoVNC\"/g" /etc/nginx/sites-enabled/web.conf
195
+
196
+ fi
197
+
198
+ sed -i "s/listen 8006 default_server;/listen $WEB_PORT default_server;/g" /etc/nginx/sites-enabled/web.conf
199
+ 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
200
+
201
+ # shellcheck disable=SC2143
202
+ if [ -f /proc/net/if_inet6 ] && [ -n "$(ifconfig -a | grep inet6)" ]; then
203
+
204
+ sed -i "s/listen $WEB_PORT default_server;/listen [::]:$WEB_PORT default_server ipv6only=off;/g" /etc/nginx/sites-enabled/web.conf
205
+
206
+ fi
207
+
208
+ # Start webserver
209
+ nginx -e stderr
210
+
211
+ fi
212
+
213
+ return 0
src/start.sh ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+
4
+ # Override this placeholder file using a Docker bind to execute a script during startup!
src/utils.sh ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ escape () {
71
+ local s
72
+ s=${1//&/\&amp;}
73
+ s=${s//</\&lt;}
74
+ s=${s//>/\&gt;}
75
+ s=${s//'"'/\&quot;}
76
+ printf -- %s "$s"
77
+ return 0
78
+ }
79
+
80
+ html() {
81
+ local title
82
+ local body
83
+ local script
84
+ local footer
85
+
86
+ title=$(escape "$APP")
87
+ title="<title>$title</title>"
88
+ footer=$(escape "$FOOTER1")
89
+
90
+ body=$(escape "$1")
91
+ if [[ "$body" == *"..." ]]; then
92
+ body="<p class=\"loading\">${body/.../}</p>"
93
+ fi
94
+
95
+ [ -n "${2:-}" ] && script="$2" || script=""
96
+
97
+ local HTML
98
+ HTML=$(<"$TEMPLATE")
99
+ HTML="${HTML/\[1\]/$title}"
100
+ HTML="${HTML/\[2\]/$script}"
101
+ HTML="${HTML/\[3\]/$body}"
102
+ HTML="${HTML/\[4\]/$footer}"
103
+ HTML="${HTML/\[5\]/$FOOTER2}"
104
+
105
+ echo "$HTML" > "$PAGE"
106
+ echo "$body" > "$INFO"
107
+
108
+ return 0
109
+ }
110
+
111
+ cpu() {
112
+ local ret
113
+ local cpu=""
114
+
115
+ ret=$(lscpu)
116
+
117
+ if grep -qi "model name" <<< "$ret"; then
118
+ 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')
119
+ fi
120
+
121
+ if [ -z "${cpu// /}" ] && grep -qi "model:" <<< "$ret"; then
122
+ 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')
123
+ fi
124
+
125
+ cpu="${cpu// CPU/}"
126
+ cpu="${cpu// [0-9] Core}"
127
+ cpu="${cpu// [0-9][0-9] Core}"
128
+ cpu="${cpu// [0-9][0-9][0-9] Core}"
129
+ cpu="${cpu//[0-9]th Gen }"
130
+ cpu="${cpu//[0-9][0-9]th Gen }"
131
+ cpu="${cpu// Processor/}"
132
+ cpu="${cpu// Quad core/}"
133
+ cpu="${cpu// Dual core/}"
134
+ cpu="${cpu// Octa core/}"
135
+ cpu="${cpu// Hexa core/}"
136
+ cpu="${cpu// Core TM/ Core}"
137
+ cpu="${cpu// with Radeon Graphics/}"
138
+ cpu="${cpu// with Radeon Vega Graphics/}"
139
+ cpu="${cpu// with Radeon Vega Mobile Gfx/}"
140
+ cpu="${cpu// w Radeon [0-9][0-9][0-9]M Graphics/}"
141
+
142
+ [ -z "${cpu// /}" ] && cpu="Unknown"
143
+
144
+ echo "$cpu"
145
+ return 0
146
+ }
147
+
148
+ hasDisk() {
149
+
150
+ [ -b "/disk" ] && return 0
151
+ [ -b "/disk1" ] && return 0
152
+ [ -b "/dev/disk1" ] && return 0
153
+ [ -b "${DEVICE:-}" ] && return 0
154
+
155
+ [ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
156
+ [ -s "$STORAGE/$DISK_NAME.img" ] && return 0
157
+ [ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
158
+
159
+ return 1
160
+ }
161
+
162
+ 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,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 /websockify {
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:5700/;
58
+ }
59
+
60
+ }
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,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ document.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
+ var err = "Error: " + e.message;
39
+ console.log(err);
40
+ setError(err);
41
+ }
42
+ }
43
+
44
+ function processInfo() {
45
+ try {
46
+
47
+ if (request.readyState != 4) {
48
+ return true;
49
+ }
50
+
51
+ var msg = request.responseText;
52
+ if (msg == null || msg.length == 0) {
53
+ setError("Lost connection");
54
+ schedule();
55
+ return false;
56
+ }
57
+
58
+ var notFound = (request.status == 404);
59
+
60
+ if (request.status == 200) {
61
+ if (msg.toLowerCase().indexOf("<html>") !== -1) {
62
+ notFound = true;
63
+ } else {
64
+ setInfo(msg);
65
+ schedule();
66
+ return true;
67
+ }
68
+ }
69
+
70
+ if (notFound) {
71
+ setInfo("Connecting to VNC", true);
72
+
73
+ var protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
74
+ var path = window.location.pathname.replace(/[^/]*$/, '').replace(/\/$/, '');
75
+ var wsUrl = protocol + "//" + window.location.host + path + "/websockify";
76
+ var webSocket = webSocketFactory.connect(wsUrl);
77
+
78
+ return true;
79
+ }
80
+
81
+ setError("Error: Received statuscode " + request.status);
82
+ schedule();
83
+ return false;
84
+
85
+ } catch (e) {
86
+ var err = "Error: " + e.message;
87
+ console.log(err);
88
+ setError(err);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ function setInfo(msg, loading, error) {
94
+ try {
95
+
96
+ if (msg == null || msg.length == 0) {
97
+ return false;
98
+ }
99
+
100
+ var el = document.getElementById("spinner");
101
+
102
+ error = !!error;
103
+ if (!error) {
104
+ el.style.visibility = 'visible';
105
+ } else {
106
+ el.style.visibility = 'hidden';
107
+ }
108
+
109
+ loading = !!loading;
110
+ if (loading) {
111
+ msg = "<p class=\"loading\">" + msg + "</p>";
112
+ }
113
+
114
+ el = document.getElementById("info");
115
+
116
+ if (el.innerHTML != msg) {
117
+ el.innerHTML = msg;
118
+ }
119
+
120
+ return true;
121
+
122
+ } catch (e) {
123
+ console.log("Error: " + e.message);
124
+ return false;
125
+ }
126
+ }
127
+
128
+ function setError(text) {
129
+ return setInfo(text, false, true);
130
+ }
131
+
132
+ function schedule() {
133
+ setTimeout(getInfo, interval);
134
+ }
135
+
136
+ schedule();