Compare commits
41 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9546b11150 | ||
![]() |
ffc4bb39c2 | ||
![]() |
41da658176 | ||
![]() |
caa7bc311d | ||
![]() |
72738ce2a3 | ||
![]() |
d5cb0b8c71 | ||
![]() |
038c4c6e14 | ||
![]() |
f89cb831c4 | ||
![]() |
a100af1fc5 | ||
![]() |
56cb411a23 | ||
![]() |
7622677489 | ||
![]() |
18dee3c07d | ||
![]() |
105db123af | ||
![]() |
846c232bd0 | ||
![]() |
0e5cca6a1e | ||
![]() |
cd018a0cca | ||
![]() |
be36019608 | ||
![]() |
8f70833e1f | ||
![]() |
29adf083b7 | ||
![]() |
95848f01a0 | ||
![]() |
469875fed4 | ||
![]() |
5659b0245d | ||
![]() |
4177dcd59b | ||
![]() |
ca650b2ffc | ||
![]() |
61a0db36b4 | ||
![]() |
ad2458ea42 | ||
![]() |
226ecd081a | ||
![]() |
5b8d5059e5 | ||
![]() |
e0a5657ad5 | ||
![]() |
ea81b4751a | ||
![]() |
b54b4986dd | ||
![]() |
68aebafaf3 | ||
![]() |
e75dceb3b6 | ||
![]() |
3b9e436c3d | ||
![]() |
66b70a8fe1 | ||
![]() |
f15feef69b | ||
![]() |
824c7a42f4 | ||
![]() |
87fbfecd92 | ||
![]() |
b6eee86366 | ||
![]() |
7014ab3ac9 | ||
![]() |
d582f6543e |
17 changed files with 133 additions and 2034 deletions
6
.devcontainer.json
Normal file
6
.devcontainer.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "qemu",
|
||||||
|
"service": "qemu",
|
||||||
|
"forwardPorts": [8006],
|
||||||
|
"dockerComposeFile": "compose.yml"
|
||||||
|
}
|
2
.github/ISSUE_TEMPLATE/1-issue.yml
vendored
2
.github/ISSUE_TEMPLATE/1-issue.yml
vendored
|
@ -21,6 +21,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Docker compose
|
label: Docker compose
|
||||||
description: The compose file (or otherwise the `docker run` command used).
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -28,6 +29,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Docker log
|
label: Docker log
|
||||||
description: The logfile of the container (as shown by `docker logs qemu`).
|
description: The logfile of the container (as shown by `docker logs qemu`).
|
||||||
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/3-bug.yml
vendored
2
.github/ISSUE_TEMPLATE/3-bug.yml
vendored
|
@ -23,6 +23,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Docker compose
|
label: Docker compose
|
||||||
description: The compose file (or otherwise the `docker run` command used).
|
description: The compose file (or otherwise the `docker run` command used).
|
||||||
|
render: yaml
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -30,6 +31,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Docker log
|
label: Docker log
|
||||||
description: The logfile of the container (as shown by `docker logs qemu`).
|
description: The logfile of the container (as shown by `docker logs qemu`).
|
||||||
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
|
@ -46,16 +46,16 @@ jobs:
|
||||||
with:
|
with:
|
||||||
context: git
|
context: git
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKERHUB_REPO }}
|
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
|
${{ secrets.DOCKERHUB_REPO }}
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,priority=100
|
type=raw,value=latest,priority=100
|
||||||
type=raw,value=${{ vars.MAJOR }}.${{ vars.MINOR }}
|
type=raw,value=${{ vars.MAJOR }}.${{ vars.MINOR }}
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.title=${{ vars.NAME }}
|
org.opencontainers.image.title=${{ vars.NAME }}
|
||||||
env:
|
env:
|
||||||
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
-
|
||||||
|
@ -112,6 +112,6 @@ jobs:
|
||||||
connection_url: ${{secrets.MAIL_CONNECTION}}
|
connection_url: ${{secrets.MAIL_CONNECTION}}
|
||||||
subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed
|
subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed
|
||||||
body: |
|
body: |
|
||||||
The build job of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} was completed successfully!
|
The build job of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} was completed successfully!
|
||||||
|
|
||||||
See https://github.com/${{ github.repository }}/actions for more information.
|
See https://github.com/${{ github.repository }}/actions for more information.
|
||||||
|
|
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
name: Run ShellCheck
|
name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@master
|
uses: ludeeus/action-shellcheck@master
|
||||||
env:
|
env:
|
||||||
SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153
|
SHELLCHECK_OPTS: -x --source-path=src -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153
|
||||||
-
|
-
|
||||||
name: Lint Dockerfile
|
name: Lint Dockerfile
|
||||||
uses: hadolint/hadolint-action@v3.1.0
|
uses: hadolint/hadolint-action@v3.1.0
|
||||||
|
|
32
Dockerfile
32
Dockerfile
|
@ -1,3 +1,6 @@
|
||||||
|
ARG VERSION_ARG="latest"
|
||||||
|
|
||||||
|
FROM qemux/qemu:${VERSION_ARG} AS src
|
||||||
FROM debian:trixie-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
ARG VERSION_ARG="0.0"
|
ARG VERSION_ARG="0.0"
|
||||||
|
@ -10,9 +13,13 @@ ARG DEBCONF_NONINTERACTIVE_SEEN="true"
|
||||||
RUN set -eu && \
|
RUN set -eu && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get --no-install-recommends -y install \
|
apt-get --no-install-recommends -y install \
|
||||||
|
bc \
|
||||||
|
jq \
|
||||||
tini \
|
tini \
|
||||||
wget \
|
wget \
|
||||||
7zip \
|
7zip \
|
||||||
|
curl \
|
||||||
|
fdisk \
|
||||||
nginx \
|
nginx \
|
||||||
procps \
|
procps \
|
||||||
seabios \
|
seabios \
|
||||||
|
@ -27,12 +34,12 @@ RUN set -eu && \
|
||||||
iputils-ping \
|
iputils-ping \
|
||||||
genisoimage \
|
genisoimage \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
netcat-openbsd \
|
qemu-system-arm && \
|
||||||
qemu-system-arm \
|
|
||||||
qemu-efi-aarch64 && \
|
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
mkdir -p /etc/qemu && \
|
mkdir -p /etc/qemu && \
|
||||||
echo "allow br0" > /etc/qemu/bridge.conf && \
|
echo "allow br0" > /etc/qemu/bridge.conf && \
|
||||||
|
wget "https://snapshot.debian.org/archive/debian/20250128T092032Z/pool/main/e/edk2/qemu-efi-aarch64_2024.11-5_all.deb" -O /tmp/aavmf.deb -q --timeout=10 && \
|
||||||
|
dpkg -i /tmp/aavmf.deb && \
|
||||||
mkdir -p /usr/share/novnc && \
|
mkdir -p /usr/share/novnc && \
|
||||||
wget "https://github.com/novnc/noVNC/archive/refs/tags/v${VERSION_VNC}.tar.gz" -O /tmp/novnc.tar.gz -q --timeout=10 && \
|
wget "https://github.com/novnc/noVNC/archive/refs/tags/v${VERSION_VNC}.tar.gz" -O /tmp/novnc.tar.gz -q --timeout=10 && \
|
||||||
tar -xf /tmp/novnc.tar.gz -C /tmp/ && \
|
tar -xf /tmp/novnc.tar.gz -C /tmp/ && \
|
||||||
|
@ -43,22 +50,19 @@ RUN set -eu && \
|
||||||
echo "$VERSION_ARG" > /run/version && \
|
echo "$VERSION_ARG" > /run/version && \
|
||||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
COPY --chmod=755 ./src /run/
|
COPY --from=src /run/*.sh /run
|
||||||
|
COPY --from=src /var/www /var/www
|
||||||
|
COPY --from=src /usr/share/novnc /usr/share/novnc
|
||||||
|
COPY --from=src /etc/nginx/sites-enabled /etc/nginx/sites-enabled
|
||||||
|
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/index.html /var/www/index.html
|
COPY --chmod=755 ./src /run/
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/js/script.js /var/www/js/script.js
|
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/css/style.css /var/www/css/style.css
|
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/img/favicon.svg /var/www/img/favicon.svg
|
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/conf/defaults.json /usr/share/novnc
|
|
||||||
ADD --chmod=664 https://raw.githubusercontent.com/qemus/qemu/master/web/conf/mandatory.json /usr/share/novnc
|
|
||||||
ADD --chmod=744 https://raw.githubusercontent.com/qemus/qemu/master/web/conf/nginx.conf /etc/nginx/sites-enabled/web.conf
|
|
||||||
|
|
||||||
VOLUME /storage
|
VOLUME /storage
|
||||||
EXPOSE 22 5900 8006
|
EXPOSE 22 5900 8006
|
||||||
|
|
||||||
ENV CPU_CORES="1"
|
ENV BOOT="alpine"
|
||||||
ENV RAM_SIZE="1G"
|
ENV CPU_CORES="2"
|
||||||
|
ENV RAM_SIZE="2G"
|
||||||
ENV DISK_SIZE="16G"
|
ENV DISK_SIZE="16G"
|
||||||
ENV BOOT="http://example.com/image.iso"
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
|
||||||
|
|
90
readme.md
90
readme.md
|
@ -24,7 +24,7 @@ Docker container for running ARM-based virtual machines using QEMU, for devices
|
||||||
|
|
||||||
## Usage 🐳
|
## Usage 🐳
|
||||||
|
|
||||||
Via Docker Compose:
|
##### Via Docker Compose:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
|
@ -46,25 +46,21 @@ services:
|
||||||
stop_grace_period: 2m
|
stop_grace_period: 2m
|
||||||
```
|
```
|
||||||
|
|
||||||
Via Docker CLI:
|
##### Via Docker CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm --name qemu -e "BOOT=alpine" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v ${PWD:-.}/qemu:/storage --stop-timeout 120 qemux/qemu-arm
|
docker run -it --rm --name qemu -e "BOOT=alpine" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/qemu:/storage" --stop-timeout 120 qemux/qemu-arm
|
||||||
```
|
```
|
||||||
|
|
||||||
Via Kubernetes:
|
##### Via Kubernetes:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/master/kubernetes.yml
|
kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/master/kubernetes.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility ⚙️
|
##### Via Github Codespaces:
|
||||||
|
|
||||||
| **Product** | **Platform** | |
|
[](https://codespaces.new/qemus/qemu)
|
||||||
|---|---|---|
|
|
||||||
| Docker Engine | Linux| ✅ |
|
|
||||||
| Docker Desktop | Linux | ❌ |
|
|
||||||
| Docker Desktop | macOS | ❌ |
|
|
||||||
|
|
||||||
## FAQ 💬
|
## FAQ 💬
|
||||||
|
|
||||||
|
@ -74,7 +70,7 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
- Set the `BOOT` variable to the [operating system](#how-do-i-select-the-operating-system) you want to install.
|
- Set the `BOOT` variable to the [operating system](#how-do-i-select-the-operating-system) you want to install.
|
||||||
|
|
||||||
- Start the container and connect to [port 8006](http://localhost:8006) using your web browser.
|
- Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser.
|
||||||
|
|
||||||
- You will see the screen and can now install the OS of your choice using your keyboard and mouse.
|
- You will see the screen and can now install the OS of your choice using your keyboard and mouse.
|
||||||
|
|
||||||
|
@ -82,7 +78,7 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
### How do I select the operating system?
|
### How do I select the operating system?
|
||||||
|
|
||||||
You can use the `BOOT` environment variable in order to specify the operating system to be installed:
|
You can use the `BOOT` environment variable in order to specify the operating system that will be downloaded:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
|
@ -93,21 +89,23 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
| **Value** | **Operating System** | **Size** |
|
| **Value** | **Operating System** | **Size** |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `alma` | Alma Linux | 1.7 GB |
|
| `alma` | Alma Linux | 1.7 GB |
|
||||||
| `alpine` | Alpine Linux | 60 MB |
|
| `alpine` | Alpine Linux | 60 MB |
|
||||||
| `centos` | CentOS Stream | 6.4 GB |
|
| `cachy` | CachyOS | 2.6 GB |
|
||||||
| `debian` | Debian | 3.7 GB |
|
| `centos` | CentOS | 6.4 GB |
|
||||||
| `fedora` | Fedora | 2.9 GB |
|
| `debian` | Debian | 3.7 GB |
|
||||||
| `gentoo` | Gentoo | 1.3 GB |
|
| `fedora` | Fedora | 2.9 GB |
|
||||||
| `kali` | Kali Linux | 3.4 GB |
|
| `gentoo` | Gentoo | 1.3 GB |
|
||||||
| `nixos` | NixOS | 2.4 GB |
|
| `kali` | Kali Linux | 3.4 GB |
|
||||||
| `oracle` | Oracle Linux | 1.0 GB |
|
| `nixos` | NixOS | 2.4 GB |
|
||||||
| `rocky` | Rocky Linux | 1.9 GB |
|
| `suse` | OpenSUSE | 1.0 GB |
|
||||||
| `ubuntu` | Ubuntu Desktop | 3.3 GB |
|
| `oracle` | Oracle Linux | 1.0 GB |
|
||||||
| `ubuntus` | Ubuntu Server | 2.7 GB |
|
| `rocky` | Rocky Linux | 1.9 GB |
|
||||||
|
| `ubuntu` | Ubuntu Desktop | 3.3 GB |
|
||||||
|
| `ubuntus` | Ubuntu Server | 2.7 GB |
|
||||||
|
|
||||||
### How can I use my own image?
|
### How can I use my own image?
|
||||||
|
|
||||||
If you want to boot an operating system that is not in the list, you can set the `BOOT` variable to the URL of the image:
|
If you want to download an operating system that is not in the list above, you can set the `BOOT` variable to the URL of the image:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
|
@ -127,17 +125,17 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
| `.vhdx` | Hyper-V |
|
| `.vhdx` | Hyper-V |
|
||||||
| `.vdi` | VirtualBox |
|
| `.vdi` | VirtualBox |
|
||||||
|
|
||||||
It will also accept `.img.gz`, `.qcow2.xz`, `.iso.zip` and many more, as it automaticly extracts compressed files.
|
It will also accept files such as `.img.gz`, `.qcow2.xz`, `.iso.zip` and many more, because it will automaticly extract compressed files.
|
||||||
|
|
||||||
You can also use a local image file directly, and skip the download altogether, by binding it in your compose file like this:
|
|
||||||
|
|
||||||
|
Alternatively you can use a local image file directly, by binding it in your compose file:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
volumes:
|
volumes:
|
||||||
- ./example.iso:/boot.iso
|
- ./example.iso:/boot.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
This way you can supply a `/boot.iso`, `/boot.img` or a `/boot.qcow2` file. The value of `BOOT` will be ignored in this case.
|
This way you can supply either a `/boot.iso`, `/boot.img` or a `/boot.qcow2` file. The value of `BOOT` will be ignored in this case.
|
||||||
|
|
||||||
### How do I change the storage location?
|
### How do I change the storage location?
|
||||||
|
|
||||||
To change the storage location, include the following bind mount in your compose file:
|
To change the storage location, include the following bind mount in your compose file:
|
||||||
|
@ -163,13 +161,13 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
### How do I change the amount of CPU or RAM?
|
### How do I change the amount of CPU or RAM?
|
||||||
|
|
||||||
By default, the container will be allowed to use a maximum of 1 CPU core and 1 GB of RAM.
|
By default, the container will be allowed to use a maximum of 2 CPU cores and 2 GB of RAM.
|
||||||
|
|
||||||
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
If you want to adjust this, you can specify the desired amount using the following environment variables:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
RAM_SIZE: "4G"
|
RAM_SIZE: "8G"
|
||||||
CPU_CORES: "4"
|
CPU_CORES: "4"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -199,9 +197,16 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
### How do I verify if my system supports KVM?
|
### How do I verify if my system supports KVM?
|
||||||
|
|
||||||
Only Linux and Windows 11 support KVM virtualization, macOS and Windows 10 do not unfortunately.
|
First check if your software is compatible using this chart:
|
||||||
|
|
||||||
You can run the following commands in Linux to check your system:
|
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
|
||||||
|
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
|
After that you can run the following commands in Linux to check your system:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install cpu-checker
|
sudo apt install cpu-checker
|
||||||
|
@ -216,11 +221,18 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
|
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
|
||||||
|
|
||||||
If you do not receive any error from `kvm-ok` but the container still complains about KVM, please check whether:
|
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
|
||||||
|
|
||||||
- you are not using "Docker Desktop for Linux" as it does not support KVM, instead make use of Docker Engine directly.
|
### How do I expose network ports?
|
||||||
|
|
||||||
- it could help to add `privileged: true` to your compose file (or `sudo` to your `docker run` command), to rule out any permission issue.
|
You can expose ports just by adding them to your compose file. If you want to be able to connect to the SSH service of the machine for example, you would add it like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ports:
|
||||||
|
- 2222:22
|
||||||
|
```
|
||||||
|
|
||||||
|
This will make port 2222 on your host redirect to port 22 of the virtual machine.
|
||||||
|
|
||||||
### How do I assign an individual IP address to the container?
|
### How do I assign an individual IP address to the container?
|
||||||
|
|
||||||
|
@ -289,12 +301,12 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
|
||||||
|
|
||||||
### How do I pass-through a disk?
|
### How do I pass-through a disk?
|
||||||
|
|
||||||
It is possible to pass-through disk devices directly by adding them to your compose file in this way:
|
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
devices:
|
devices:
|
||||||
- /dev/sdb:/disk1
|
- /dev/sdb:/disk1
|
||||||
- /dev/sdc:/disk2
|
- /dev/sdc1:/disk2
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `/disk1` if you want it to become your main drive, and use `/disk2` and higher to add them as secondary drives.
|
Use `/disk1` if you want it to become your main drive, and use `/disk2` and higher to add them as secondary drives.
|
||||||
|
|
22
src/boot.sh
22
src/boot.sh
|
@ -75,26 +75,42 @@ esac
|
||||||
MSRS="/sys/module/kvm/parameters/ignore_msrs"
|
MSRS="/sys/module/kvm/parameters/ignore_msrs"
|
||||||
if [ -e "$MSRS" ]; then
|
if [ -e "$MSRS" ]; then
|
||||||
result=$(<"$MSRS")
|
result=$(<"$MSRS")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then
|
if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then
|
||||||
echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
|
echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CLOCKSOURCE="tsc"
|
CLOCKSOURCE="tsc"
|
||||||
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||||
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||||
|
|
||||||
if [ ! -f "$CLOCK" ]; then
|
if [ ! -f "$CLOCK" ]; then
|
||||||
warn "file \"$CLOCK\" cannot not found?"
|
warn "file \"$CLOCK\" cannot not found?"
|
||||||
else
|
else
|
||||||
result=$(<"$CLOCK")
|
result=$(<"$CLOCK")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
case "${result,,}" in
|
case "${result,,}" in
|
||||||
"${CLOCKSOURCE,,}" ) ;;
|
"${CLOCKSOURCE,,}" ) ;;
|
||||||
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
||||||
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
||||||
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'" ;;
|
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'" ;;
|
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SM_BIOS=""
|
||||||
|
PS="/sys/class/dmi/id/product_serial"
|
||||||
|
|
||||||
|
if [ -s "$PS" ] && [ -r "$PS" ]; then
|
||||||
|
|
||||||
|
BIOS_SERIAL=$(<"$PS")
|
||||||
|
BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}"
|
||||||
|
|
||||||
|
if [ -n "$BIOS_SERIAL" ]; then
|
||||||
|
SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -3,7 +3,7 @@ set -Eeuo pipefail
|
||||||
|
|
||||||
: "${UUID:=""}"
|
: "${UUID:=""}"
|
||||||
: "${SERIAL:="mon:stdio"}"
|
: "${SERIAL:="mon:stdio"}"
|
||||||
: "${USB:="qemu-xhci,id=xhci"}"
|
: "${USB:="qemu-xhci,id=xhci,p2=7,p3=7"}"
|
||||||
: "${MONITOR:="telnet:localhost:7100,server,nowait,nodelay"}"
|
: "${MONITOR:="telnet:localhost:7100,server,nowait,nodelay"}"
|
||||||
: "${SMP:="$CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"}"
|
: "${SMP:="$CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"}"
|
||||||
|
|
||||||
|
@ -12,20 +12,25 @@ SERIAL_OPTS="-serial $SERIAL"
|
||||||
CPU_OPTS="-cpu $CPU_FLAGS -smp $SMP"
|
CPU_OPTS="-cpu $CPU_FLAGS -smp $SMP"
|
||||||
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||||
MON_OPTS="-monitor $MONITOR -name $PROCESS,process=$PROCESS,debug-threads=on"
|
MON_OPTS="-monitor $MONITOR -name $PROCESS,process=$PROCESS,debug-threads=on"
|
||||||
[ -n "$USB" ] && [[ "${USB,,}" != "no"* ]] && USB_OPTS="-device $USB -device usb-kbd -device usb-tablet"
|
|
||||||
MAC_OPTS="-machine type=${MACHINE},secure=${SECURE},dump-guest-core=off${KVM_OPTS}"
|
MAC_OPTS="-machine type=${MACHINE},secure=${SECURE},dump-guest-core=off${KVM_OPTS}"
|
||||||
[ -n "$UUID" ] && MAC_OPTS="$MAC_OPTS -uuid $UUID"
|
|
||||||
|
[ -n "$UUID" ] && MAC_OPTS+=" -uuid $UUID"
|
||||||
|
[ -n "$SM_BIOS" ] && MAC_OPTS+=" $SM_BIOS"
|
||||||
|
|
||||||
DEV_OPTS="-object rng-random,id=objrng0,filename=/dev/urandom"
|
DEV_OPTS="-object rng-random,id=objrng0,filename=/dev/urandom"
|
||||||
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0"
|
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0"
|
||||||
|
|
||||||
if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
|
if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
|
||||||
DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=pcie.0"
|
DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=pcie.0"
|
||||||
if [ -d "/shared" ]; then
|
|
||||||
DEV_OPTS+=" -fsdev local,id=fsdev0,path=/shared,security_model=none"
|
|
||||||
DEV_OPTS+=" -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=shared"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -d "/shared" ] && [[ "${BOOT_MODE,,}" != "windows"* ]]; then
|
||||||
|
DEV_OPTS+=" -fsdev local,id=fsdev0,path=/shared,security_model=none"
|
||||||
|
DEV_OPTS+=" -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=shared"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "$USB" ] && [[ "${USB,,}" != "no"* ]] && USB_OPTS="-device $USB -device usb-kbd -device usb-tablet"
|
||||||
|
|
||||||
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"
|
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"
|
||||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||||
|
|
||||||
|
@ -67,7 +72,7 @@ if [[ "$RAM_CHECK" != [Nn]* ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$DEBUG" == [Yy1]* ]]; then
|
if [[ "$DEBUG" == [Yy1]* ]]; then
|
||||||
printf "Arguments:\n\n%s" "${ARGS// -/$'\n-'}" && echo
|
printf "Arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
getURL() {
|
|
||||||
local id="${1/ /}"
|
|
||||||
local ret="$2"
|
|
||||||
local url=""
|
|
||||||
local name=""
|
|
||||||
|
|
||||||
case "${id,,}" in
|
|
||||||
"alma" )
|
|
||||||
name="AlmaLinux"
|
|
||||||
url="https://repo.almalinux.org/almalinux/9/live/aarch64/AlmaLinux-9.5-aarch64-Live-GNOME.iso" ;;
|
|
||||||
"alpine" )
|
|
||||||
name="Alpine Linux"
|
|
||||||
url="https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/aarch64/alpine-virt-3.19.1-aarch64.iso" ;;
|
|
||||||
"arch" )
|
|
||||||
name="Arch Linux"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"cachy" | "cachyos" )
|
|
||||||
name="CachyOS"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"centos" )
|
|
||||||
name="CentOS Stream"
|
|
||||||
url="https://mirrors.xtom.de/centos-stream/10-stream/BaseOS/aarch64/iso/CentOS-Stream-10-latest-aarch64-dvd1.iso" ;;
|
|
||||||
"debian" )
|
|
||||||
name="Debian"
|
|
||||||
url="https://cdimage.debian.org/debian-cd/current/arm64/iso-dvd/debian-12.9.0-arm64-DVD-1.iso" ;;
|
|
||||||
"endeavour" | "endeavouros" )
|
|
||||||
name="EndeavourOS"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"fedora" )
|
|
||||||
name="Fedora Linux"
|
|
||||||
url="https://eu.edge.kernel.org/fedora/releases/41/Workstation/aarch64/images/Fedora-Workstation-41-1.4.aarch64.raw.xz" ;;
|
|
||||||
"gentoo" )
|
|
||||||
name="Gentoo Linux"
|
|
||||||
url="https://distfiles.gentoo.org/releases/arm64/autobuilds/20250309T234826Z/di-arm64-cloudinit-20250309T234826Z.qcow2" ;;
|
|
||||||
"kali" )
|
|
||||||
name="Kali Linux"
|
|
||||||
url="https://cdimage.kali.org/kali-2024.4/kali-linux-2024.4-live-arm64.iso" ;;
|
|
||||||
"kubuntu" )
|
|
||||||
name="Kubuntu"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"macos" | "osx" )
|
|
||||||
name="macOS"
|
|
||||||
error "To install $name use: https://github.com/dockur/macos" && return 1 ;;
|
|
||||||
"mint" | "linuxmint" )
|
|
||||||
name="Linux Mint"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"manjaro" )
|
|
||||||
name="Manjaro"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"mx" )
|
|
||||||
name="MX Linux"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"nixos" )
|
|
||||||
name="NixOS"
|
|
||||||
url="https://channels.nixos.org/nixos-24.11/latest-nixos-gnome-aarch64-linux.iso" ;;
|
|
||||||
"opensuse" | "suse" )
|
|
||||||
name="OpenSUSE"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"oracle" )
|
|
||||||
name="Oracle Linux"
|
|
||||||
url="https://yum.oracle.com/ISOS/OracleLinux/OL9/u5/aarch64/OracleLinux-R9-U5-aarch64-boot-uek.iso" ;;
|
|
||||||
"rocky" )
|
|
||||||
name="Rocky Linux"
|
|
||||||
url="https://dl.rockylinux.org/pub/rocky/9/live/aarch64/Rocky-9-Workstation-aarch64-latest.iso" ;;
|
|
||||||
"slack" | "slackware" )
|
|
||||||
name="Slackware"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"tails" )
|
|
||||||
name="Tails"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
"ubuntu" )
|
|
||||||
name="Ubuntu Desktop"
|
|
||||||
url="https://cdimage.ubuntu.com/ubuntu/releases/24.10/release/ubuntu-24.10-desktop-arm64.iso" ;;
|
|
||||||
"ubuntus" )
|
|
||||||
name="Ubuntu Server"
|
|
||||||
url="https://cdimage.ubuntu.com/releases/24.04/release/ubuntu-24.04.2-live-server-arm64.iso" ;;
|
|
||||||
"windows" )
|
|
||||||
name="Windows"
|
|
||||||
error "To install $name use: https://github.com/dockur/windows" && return 1 ;;
|
|
||||||
"xubuntu" )
|
|
||||||
name="Xubuntu"
|
|
||||||
error "No image for $name is available for ARM64 yet! " && return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${ret,,}" in
|
|
||||||
"test" ) ;;
|
|
||||||
"name" ) echo "$name" ;;
|
|
||||||
*) echo "$url";;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
681
src/disk.sh
681
src/disk.sh
|
@ -1,681 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
|
|
||||||
: "${DISK_FMT:=""}" # Disk file format, can be set to "raw" (default) or "qcow2"
|
|
||||||
: "${DISK_TYPE:=""}" # Device type to be used, "sata", "nvme", "blk" or "scsi"
|
|
||||||
: "${DISK_FLAGS:=""}" # Specifies the options for use with the qcow2 disk format
|
|
||||||
: "${DISK_CACHE:="none"}" # Caching mode, can be set to 'writeback' for better performance
|
|
||||||
: "${DISK_DISCARD:="on"}" # Controls whether unmap (TRIM) commands are passed to the host.
|
|
||||||
: "${DISK_ROTATION:="1"}" # Rotation rate, set to 1 for SSD storage and increase for HDD
|
|
||||||
|
|
||||||
fmt2ext() {
|
|
||||||
local DISK_FMT=$1
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
qcow2)
|
|
||||||
echo "qcow2"
|
|
||||||
;;
|
|
||||||
raw)
|
|
||||||
echo "img"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
ext2fmt() {
|
|
||||||
local DISK_EXT=$1
|
|
||||||
|
|
||||||
case "${DISK_EXT,,}" in
|
|
||||||
qcow2)
|
|
||||||
echo "qcow2"
|
|
||||||
;;
|
|
||||||
img)
|
|
||||||
echo "raw"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized file extension: .$DISK_EXT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
getSize() {
|
|
||||||
local DISK_FILE=$1
|
|
||||||
local DISK_EXT DISK_FMT
|
|
||||||
|
|
||||||
DISK_EXT=$(echo "${DISK_FILE//*./}" | sed 's/^.*\.//')
|
|
||||||
DISK_FMT=$(ext2fmt "$DISK_EXT")
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
stat -c%s "$DISK_FILE"
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
qemu-img info "$DISK_FILE" -f "$DISK_FMT" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/'
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error "Unrecognized disk format: $DISK_FMT" && exit 78
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
isCow() {
|
|
||||||
local FS=$1
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "btrfs" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsDirect() {
|
|
||||||
local FS=$1
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" ]] || [[ "${FS,,}" == "tmpfs" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
createDisk() {
|
|
||||||
|
|
||||||
local DISK_FILE=$1
|
|
||||||
local DISK_SPACE=$2
|
|
||||||
local DISK_DESC=$3
|
|
||||||
local DISK_FMT=$4
|
|
||||||
local FS=$5
|
|
||||||
local DATA_SIZE DIR SPACE GB FA
|
|
||||||
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
|
|
||||||
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
if (( DATA_SIZE > SPACE )); then
|
|
||||||
GB=$(formatBytes "$SPACE")
|
|
||||||
error "Not enough free space to create a $DISK_DESC of ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available..."
|
|
||||||
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 76
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
html "Creating a $DISK_DESC image..."
|
|
||||||
info "Creating a ${DISK_SPACE/G/ GB} $DISK_STYLE $DISK_DESC image in $DISK_FMT format..."
|
|
||||||
|
|
||||||
local FAIL="Could not create a $DISK_STYLE $DISK_FMT $DISK_DESC image of ${DISK_SPACE/G/ GB} ($DISK_FILE)"
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
|
|
||||||
if isCow "$FS"; then
|
|
||||||
if ! touch "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
{ chattr +C "$DISK_FILE"; } || :
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Create an empty file
|
|
||||||
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && exit 77
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
|
|
||||||
local DISK_PARAM="$DISK_ALLOC"
|
|
||||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
|
||||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
|
||||||
|
|
||||||
if ! qemu-img create -f "$DISK_FMT" -o "$DISK_PARAM" -- "$DISK_FILE" "$DATA_SIZE" ; then
|
|
||||||
rm -f "$DISK_FILE"
|
|
||||||
error "$FAIL" && exit 70
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if isCow "$FS"; then
|
|
||||||
FA=$(lsattr "$DISK_FILE")
|
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
|
||||||
error "Failed to disable COW for $DISK_DESC image $DISK_FILE on ${FS^^} filesystem (returned $FA)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeDisk() {
|
|
||||||
|
|
||||||
local DISK_FILE=$1
|
|
||||||
local DISK_SPACE=$2
|
|
||||||
local DISK_DESC=$3
|
|
||||||
local DISK_FMT=$4
|
|
||||||
local FS=$5
|
|
||||||
local CUR_SIZE DATA_SIZE DIR SPACE GB
|
|
||||||
|
|
||||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
|
|
||||||
local REQ=$((DATA_SIZE-CUR_SIZE))
|
|
||||||
(( REQ < 1 )) && error "Shrinking disks is not supported yet, please increase ${DISK_DESC^^}_SIZE." && exit 71
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
if (( REQ > SPACE )); then
|
|
||||||
GB=$(formatBytes "$SPACE")
|
|
||||||
error "Not enough free space to resize $DISK_DESC to ${DISK_SPACE/G/ GB} in $DIR, it has only $GB available.."
|
|
||||||
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
GB=$(formatBytes "$CUR_SIZE")
|
|
||||||
MSG="Resizing $DISK_DESC from $GB to ${DISK_SPACE/G/ GB}..."
|
|
||||||
info "$MSG" && html "$MSG"
|
|
||||||
|
|
||||||
local FAIL="Could not resize the $DISK_STYLE $DISK_FMT $DISK_DESC image from ${GB} to ${DISK_SPACE/G/ GB} ($DISK_FILE)"
|
|
||||||
|
|
||||||
case "${DISK_FMT,,}" in
|
|
||||||
raw)
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Resize file by changing its length
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 75
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
# Resize file by allocating more space
|
|
||||||
if ! fallocate -l "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
if ! truncate -s "$DATA_SIZE" "$DISK_FILE"; then
|
|
||||||
error "$FAIL" && exit 75
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
qcow2)
|
|
||||||
|
|
||||||
if ! qemu-img resize -f "$DISK_FMT" "--$DISK_ALLOC" "$DISK_FILE" "$DATA_SIZE" ; then
|
|
||||||
error "$FAIL" && exit 72
|
|
||||||
fi
|
|
||||||
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
convertDisk() {
|
|
||||||
|
|
||||||
local SOURCE_FILE=$1
|
|
||||||
local SOURCE_FMT=$2
|
|
||||||
local DST_FILE=$3
|
|
||||||
local DST_FMT=$4
|
|
||||||
local DISK_BASE=$5
|
|
||||||
local DISK_DESC=$6
|
|
||||||
local FS=$7
|
|
||||||
|
|
||||||
[ -f "$DST_FILE" ] && error "Conversion failed, destination file $DST_FILE already exists?" && exit 79
|
|
||||||
[ ! -f "$SOURCE_FILE" ] && error "Conversion failed, source file $SOURCE_FILE does not exists?" && exit 79
|
|
||||||
|
|
||||||
local TMP_FILE="$DISK_BASE.tmp"
|
|
||||||
rm -f "$TMP_FILE"
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
|
|
||||||
local DIR CUR_SIZE SPACE GB
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
DIR=$(dirname "$TMP_FILE")
|
|
||||||
CUR_SIZE=$(getSize "$SOURCE_FILE")
|
|
||||||
SPACE=$(df --output=avail -B 1 "$DIR" | tail -n 1)
|
|
||||||
|
|
||||||
if (( CUR_SIZE > SPACE )); then
|
|
||||||
GB=$(formatBytes "$SPACE")
|
|
||||||
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $GB available..."
|
|
||||||
error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
local msg="Converting $DISK_DESC to $DST_FMT"
|
|
||||||
html "$msg..."
|
|
||||||
info "$msg, please wait until completed..."
|
|
||||||
|
|
||||||
local CONV_FLAGS="-p"
|
|
||||||
local DISK_PARAM="$DISK_ALLOC"
|
|
||||||
isCow "$FS" && DISK_PARAM+=",nocow=on"
|
|
||||||
|
|
||||||
if [[ "$DST_FMT" != "raw" ]]; then
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
CONV_FLAGS+=" -c"
|
|
||||||
fi
|
|
||||||
[ -n "$DISK_FLAGS" ] && DISK_PARAM+=",$DISK_FLAGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
if ! qemu-img convert -f "$SOURCE_FMT" $CONV_FLAGS -o "$DISK_PARAM" -O "$DST_FMT" -- "$SOURCE_FILE" "$TMP_FILE"; then
|
|
||||||
rm -f "$TMP_FILE"
|
|
||||||
error "Failed to convert $DISK_STYLE $DISK_DESC image to $DST_FMT format in $DIR, is there enough space available?" && exit 79
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$DST_FMT" == "raw" ]]; then
|
|
||||||
if [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
# Work around qemu-img bug
|
|
||||||
CUR_SIZE=$(stat -c%s "$TMP_FILE")
|
|
||||||
if ! fallocate -l "$CUR_SIZE" "$TMP_FILE"; then
|
|
||||||
error "Failed to allocate $CUR_SIZE bytes for $DISK_DESC image $TMP_FILE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$SOURCE_FILE"
|
|
||||||
mv "$TMP_FILE" "$DST_FILE"
|
|
||||||
|
|
||||||
if isCow "$FS"; then
|
|
||||||
FA=$(lsattr "$DST_FILE")
|
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
|
||||||
error "Failed to disable COW for $DISK_DESC image $DST_FILE on ${FS^^} filesystem (returned $FA)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg="Conversion of $DISK_DESC"
|
|
||||||
html "$msg completed..."
|
|
||||||
info "$msg to $DST_FMT completed succesfully!"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
checkFS () {
|
|
||||||
|
|
||||||
local FS=$1
|
|
||||||
local DISK_FILE=$2
|
|
||||||
local DISK_DESC=$3
|
|
||||||
local DIR FA
|
|
||||||
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
[ ! -d "$DIR" ] && return 0
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "overlay"* ]]; then
|
|
||||||
info "Warning: the filesystem of $DIR is OverlayFS, this usually means it was binded to an invalid path!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "fuse"* ]]; then
|
|
||||||
info "Warning: the filesystem of $DIR is FUSE, this extra layer will negatively affect performance!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! supportsDirect "$FS"; then
|
|
||||||
info "Warning: the filesystem of $DIR is $FS, which does not support O_DIRECT mode, adjusting settings..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if isCow "$FS"; then
|
|
||||||
if [ -f "$DISK_FILE" ]; then
|
|
||||||
FA=$(lsattr "$DISK_FILE")
|
|
||||||
if [[ "$FA" != *"C"* ]]; then
|
|
||||||
info "Warning: COW (copy on write) is not disabled for $DISK_DESC image file $DISK_FILE, this is recommended on ${FS^^} filesystems!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
createDevice () {
|
|
||||||
|
|
||||||
local DISK_FILE=$1
|
|
||||||
local DISK_TYPE=$2
|
|
||||||
local DISK_INDEX=$3
|
|
||||||
local DISK_ADDRESS=$4
|
|
||||||
local DISK_FMT=$5
|
|
||||||
local DISK_IO=$6
|
|
||||||
local DISK_CACHE=$7
|
|
||||||
local DISK_ID="data$DISK_INDEX"
|
|
||||||
|
|
||||||
local index=""
|
|
||||||
[ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
|
|
||||||
local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"none" ) ;;
|
|
||||||
"auto" )
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"usb" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device usb-storage,drive=${DISK_ID}${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"nvme" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"ide" | "sata" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
|
|
||||||
-device ide-hd,drive=${DISK_ID},bus=ahci$DISK_INDEX.0,rotation_rate=$DISK_ROTATION${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"blk" | "virtio-blk" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"scsi" | "virtio-scsi" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
|
|
||||||
-device scsi-hd,drive=${DISK_ID},bus=${DISK_ID}b.0,channel=0,scsi-id=0,lun=0,rotation_rate=$DISK_ROTATION${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addMedia () {
|
|
||||||
|
|
||||||
local DISK_FILE=$1
|
|
||||||
local DISK_TYPE=$2
|
|
||||||
local DISK_INDEX=$3
|
|
||||||
local DISK_ADDRESS=$4
|
|
||||||
|
|
||||||
local index=""
|
|
||||||
local DISK_ID="cdrom$DISK_INDEX"
|
|
||||||
[ -n "$DISK_INDEX" ] && index=",bootindex=$DISK_INDEX"
|
|
||||||
local result=" -drive file=$DISK_FILE,id=$DISK_ID,format=raw,cache=unsafe,readonly=on,media=cdrom"
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"none" ) ;;
|
|
||||||
"auto" )
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"usb" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device usb-storage,drive=${DISK_ID}${index},removable=on"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"nvme" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device nvme,drive=${DISK_ID}${index},serial=deadbeaf${DISK_INDEX}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"ide" | "sata" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device ich9-ahci,id=ahci${DISK_INDEX},addr=$DISK_ADDRESS \
|
|
||||||
-device ide-cd,drive=${DISK_ID},bus=ahci${DISK_INDEX}.0${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"blk" | "virtio-blk" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device virtio-blk-pci,drive=${DISK_ID},bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
"scsi" | "virtio-scsi" )
|
|
||||||
result+=",if=none \
|
|
||||||
-device virtio-scsi-pci,id=${DISK_ID}b,bus=pcie.0,addr=$DISK_ADDRESS,iothread=io2 \
|
|
||||||
-device scsi-cd,drive=${DISK_ID},bus=${DISK_ID}b.0${index}"
|
|
||||||
echo "$result"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addDisk () {
|
|
||||||
|
|
||||||
local DISK_BASE=$1
|
|
||||||
local DISK_TYPE=$2
|
|
||||||
local DISK_DESC=$3
|
|
||||||
local DISK_SPACE=$4
|
|
||||||
local DISK_INDEX=$5
|
|
||||||
local DISK_ADDRESS=$6
|
|
||||||
local DISK_FMT=$7
|
|
||||||
local DISK_IO=$8
|
|
||||||
local DISK_CACHE=$9
|
|
||||||
local DISK_EXT DIR SPACE DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE
|
|
||||||
|
|
||||||
DISK_EXT=$(fmt2ext "$DISK_FMT")
|
|
||||||
local DISK_FILE="$DISK_BASE.$DISK_EXT"
|
|
||||||
|
|
||||||
DIR=$(dirname "$DISK_FILE")
|
|
||||||
[ ! -d "$DIR" ] && return 0
|
|
||||||
|
|
||||||
SPACE="${DISK_SPACE// /}"
|
|
||||||
[ -z "$SPACE" ] && SPACE="16G"
|
|
||||||
[ -z "${SPACE//[0-9. ]}" ] && SPACE="${SPACE}G"
|
|
||||||
SPACE=$(echo "${SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
|
||||||
|
|
||||||
if ! numfmt --from=iec "$SPACE" &>/dev/null; then
|
|
||||||
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
|
|
||||||
fi
|
|
||||||
|
|
||||||
DATA_SIZE=$(numfmt --from=iec "$SPACE")
|
|
||||||
|
|
||||||
if (( DATA_SIZE < 104857600 )); then
|
|
||||||
error "Please increase ${DISK_DESC^^}_SIZE to at least 100 MB." && exit 73
|
|
||||||
fi
|
|
||||||
|
|
||||||
FS=$(stat -f -c %T "$DIR")
|
|
||||||
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
|
|
||||||
|
|
||||||
if ! supportsDirect "$FS"; then
|
|
||||||
DISK_IO="threads"
|
|
||||||
DISK_CACHE="writeback"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [ -s "$DISK_FILE" ] ; then
|
|
||||||
|
|
||||||
if [[ "${DISK_FMT,,}" != "raw" ]]; then
|
|
||||||
PREV_FMT="raw"
|
|
||||||
else
|
|
||||||
PREV_FMT="qcow2"
|
|
||||||
fi
|
|
||||||
|
|
||||||
PREV_EXT=$(fmt2ext "$PREV_FMT")
|
|
||||||
|
|
||||||
if [ -s "$DISK_BASE.$PREV_EXT" ] ; then
|
|
||||||
convertDisk "$DISK_BASE.$PREV_EXT" "$PREV_FMT" "$DISK_FILE" "$DISK_FMT" "$DISK_BASE" "$DISK_DESC" "$FS" || exit $?
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -s "$DISK_FILE" ]; then
|
|
||||||
|
|
||||||
CUR_SIZE=$(getSize "$DISK_FILE")
|
|
||||||
|
|
||||||
if (( DATA_SIZE > CUR_SIZE )); then
|
|
||||||
resizeDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
createDisk "$DISK_FILE" "$SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS+=$(createDevice "$DISK_FILE" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addDevice () {
|
|
||||||
|
|
||||||
local DISK_DEV=$1
|
|
||||||
local DISK_TYPE=$2
|
|
||||||
local DISK_INDEX=$3
|
|
||||||
local DISK_ADDRESS=$4
|
|
||||||
|
|
||||||
[ -z "$DISK_DEV" ] && return 0
|
|
||||||
[ ! -b "$DISK_DEV" ] && error "Device $DISK_DEV cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
|
|
||||||
|
|
||||||
DISK_OPTS+=$(createDevice "$DISK_DEV" "$DISK_TYPE" "$DISK_INDEX" "$DISK_ADDRESS" "raw" "$DISK_IO" "$DISK_CACHE")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
html "Initializing disks..."
|
|
||||||
|
|
||||||
[ -z "${DISK_OPTS:-}" ] && DISK_OPTS=""
|
|
||||||
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE="scsi"
|
|
||||||
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
|
||||||
|
|
||||||
case "${DISK_TYPE,,}" in
|
|
||||||
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
|
|
||||||
* ) error "Invalid DISK_TYPE specified, value \"$DISK_TYPE\" is not recognized!" && exit 80 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ "${MACHINE,,}" != "virt" ]]; then
|
|
||||||
FALLBACK="ide"
|
|
||||||
else
|
|
||||||
FALLBACK="usb"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[[ "${BOOT_MODE:-}" == "windows_legacy" ]] && FALLBACK="auto"
|
|
||||||
|
|
||||||
if [ -z "${MEDIA_TYPE:-}" ]; then
|
|
||||||
if [[ "${BOOT_MODE:-}" != "windows"* ]]; then
|
|
||||||
if [[ "${DISK_TYPE,,}" == "blk" ]]; then
|
|
||||||
MEDIA_TYPE="$FALLBACK"
|
|
||||||
else
|
|
||||||
MEDIA_TYPE="$DISK_TYPE"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
MEDIA_TYPE="$FALLBACK"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${MEDIA_TYPE,,}" in
|
|
||||||
"ide" | "sata" | "nvme" | "usb" | "scsi" | "blk" | "auto" | "none" ) ;;
|
|
||||||
* ) error "Invalid MEDIA_TYPE specified, value \"$MEDIA_TYPE\" is not recognized!" && exit 80 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
|
|
||||||
case "${BOOT,,}" in
|
|
||||||
*".iso" )
|
|
||||||
DISK_OPTS+=$(addMedia "$BOOT" "$MEDIA_TYPE" "$BOOT_INDEX" "0x5") ;;
|
|
||||||
*".img" | *".raw" )
|
|
||||||
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "raw" "$DISK_IO" "$DISK_CACHE") ;;
|
|
||||||
*".qcow2" )
|
|
||||||
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "$BOOT_INDEX" "0x5" "qcow2" "$DISK_IO" "$DISK_CACHE") ;;
|
|
||||||
* )
|
|
||||||
error "Invalid BOOT image specified, extension \".${BOOT/*./}\" is not recognized!" && exit 80 ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
DRIVERS="/drivers.iso"
|
|
||||||
[ ! -f "$DRIVERS" ] || [ ! -s "$DRIVERS" ] && DRIVERS="$STORAGE/drivers.iso"
|
|
||||||
|
|
||||||
if [ -f "$DRIVERS" ] && [ -s "$DRIVERS" ]; then
|
|
||||||
DISK_OPTS+=$(addMedia "$DRIVERS" "$FALLBACK" "" "0x6")
|
|
||||||
fi
|
|
||||||
|
|
||||||
RESCUE="/start.iso"
|
|
||||||
[ ! -f "$RESCUE" ] || [ ! -s "$RESCUE" ] && RESCUE="$STORAGE/start.iso"
|
|
||||||
|
|
||||||
if [ -f "$RESCUE" ] && [ -s "$RESCUE" ]; then
|
|
||||||
DISK_OPTS+=$(addMedia "$RESCUE" "$FALLBACK" "1" "0x6")
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK1_FILE="$STORAGE/${DISK_NAME}"
|
|
||||||
DISK2_FILE="/storage2/${DISK_NAME}2"
|
|
||||||
DISK3_FILE="/storage3/${DISK_NAME}3"
|
|
||||||
DISK4_FILE="/storage4/${DISK_NAME}4"
|
|
||||||
|
|
||||||
if [ -z "$DISK_FMT" ]; then
|
|
||||||
if [ -f "$DISK1_FILE.qcow2" ]; then
|
|
||||||
DISK_FMT="qcow2"
|
|
||||||
else
|
|
||||||
if [[ "$BOOT" == *".qcow2" ]] && [ ! -f "$DISK1_FILE.img" ]; then
|
|
||||||
DISK_FMT="qcow2"
|
|
||||||
else
|
|
||||||
DISK_FMT="raw"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$ALLOCATE" ]; then
|
|
||||||
ALLOCATE="N"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
DISK_STYLE="growable"
|
|
||||||
DISK_ALLOC="preallocation=off"
|
|
||||||
else
|
|
||||||
DISK_STYLE="preallocated"
|
|
||||||
DISK_ALLOC="preallocation=falloc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
: "${DISK2_SIZE:=""}"
|
|
||||||
: "${DISK3_SIZE:=""}"
|
|
||||||
: "${DISK4_SIZE:=""}"
|
|
||||||
|
|
||||||
: "${DEVICE:=""}" # Docker variables to passthrough a block device, like /dev/vdc1.
|
|
||||||
: "${DEVICE2:=""}"
|
|
||||||
: "${DEVICE3:=""}"
|
|
||||||
: "${DEVICE4:=""}"
|
|
||||||
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/disk" ] && DEVICE="/disk"
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/disk1" ] && DEVICE="/disk1"
|
|
||||||
[ -z "$DEVICE2" ] && [ -b "/disk2" ] && DEVICE2="/disk2"
|
|
||||||
[ -z "$DEVICE3" ] && [ -b "/disk3" ] && DEVICE3="/disk3"
|
|
||||||
[ -z "$DEVICE4" ] && [ -b "/disk4" ] && DEVICE4="/disk4"
|
|
||||||
|
|
||||||
[ -z "$DEVICE" ] && [ -b "/dev/disk1" ] && DEVICE="/dev/disk1"
|
|
||||||
[ -z "$DEVICE2" ] && [ -b "/dev/disk2" ] && DEVICE2="/dev/disk2"
|
|
||||||
[ -z "$DEVICE3" ] && [ -b "/dev/disk3" ] && DEVICE3="/dev/disk3"
|
|
||||||
[ -z "$DEVICE4" ] && [ -b "/dev/disk4" ] && DEVICE4="/dev/disk4"
|
|
||||||
|
|
||||||
if [ -n "$DEVICE" ]; then
|
|
||||||
addDevice "$DEVICE" "$DISK_TYPE" "3" "0xa" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK1_FILE" "$DISK_TYPE" "disk" "$DISK_SIZE" "3" "0xa" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE2" ]; then
|
|
||||||
addDevice "$DEVICE2" "$DISK_TYPE" "4" "0xb" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK2_FILE" "$DISK_TYPE" "disk2" "$DISK2_SIZE" "4" "0xb" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE3" ]; then
|
|
||||||
addDevice "$DEVICE3" "$DISK_TYPE" "5" "0xc" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK3_FILE" "$DISK_TYPE" "disk3" "$DISK3_SIZE" "5" "0xc" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$DEVICE4" ]; then
|
|
||||||
addDevice "$DEVICE4" "$DISK_TYPE" "6" "0xd" || exit $?
|
|
||||||
else
|
|
||||||
addDisk "$DISK4_FILE" "$DISK_TYPE" "disk4" "$DISK4_SIZE" "6" "0xd" "$DISK_FMT" "$DISK_IO" "$DISK_CACHE" || exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
DISK_OPTS+=" -object iothread,id=io2"
|
|
||||||
|
|
||||||
html "Initialized disks successfully..."
|
|
||||||
return 0
|
|
|
@ -2,10 +2,13 @@
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
: "${APP:="QEMU"}"
|
: "${APP:="QEMU"}"
|
||||||
|
: "${MACHINE:="virt"}"
|
||||||
|
: "${PLATFORM:="arm64"}"
|
||||||
: "${SUPPORT:="https://github.com/qemus/qemu-arm"}"
|
: "${SUPPORT:="https://github.com/qemus/qemu-arm"}"
|
||||||
|
|
||||||
cd /run
|
cd /run
|
||||||
|
|
||||||
|
. utils.sh # Load functions
|
||||||
. reset.sh # Initialize system
|
. reset.sh # Initialize system
|
||||||
. define.sh # Define images
|
. define.sh # Define images
|
||||||
. install.sh # Download image
|
. install.sh # Download image
|
||||||
|
|
344
src/install.sh
344
src/install.sh
|
@ -1,344 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
moveFile() {
|
|
||||||
|
|
||||||
local file="$1"
|
|
||||||
local ext="${file##*.}"
|
|
||||||
local dest="$STORAGE/boot.$ext"
|
|
||||||
|
|
||||||
if [[ "$file" == "$dest" ]] || [[ "$file" == "/boot.$ext" ]]; then
|
|
||||||
BOOT="$file"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! mv -f "$file" "$dest"; then
|
|
||||||
error "Failed to move $file to $dest !"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BOOT="$dest"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
detectType() {
|
|
||||||
|
|
||||||
local dir=""
|
|
||||||
local file="$1"
|
|
||||||
|
|
||||||
[ ! -f "$file" ] && return 1
|
|
||||||
[ ! -s "$file" ] && return 1
|
|
||||||
|
|
||||||
case "${file,,}" in
|
|
||||||
*".iso" | *".img" | *".raw" | *".qcow2" ) ;;
|
|
||||||
* ) return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -n "$BOOT_MODE" ] || [[ "${file,,}" != *".iso" ]]; then
|
|
||||||
! moveFile "$file" && return 1
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Automaticly detect UEFI-compatible ISO's
|
|
||||||
dir=$(isoinfo -f -i "$file")
|
|
||||||
|
|
||||||
if [ -n "$dir" ]; then
|
|
||||||
dir=$(echo "${dir^^}" | grep "^/EFI")
|
|
||||||
[ -z "$dir" ] && BOOT_MODE="legacy"
|
|
||||||
else
|
|
||||||
error "Failed to read ISO file, invalid format!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
! moveFile "$file" && return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFile() {
|
|
||||||
|
|
||||||
local url="$1"
|
|
||||||
local base="$2"
|
|
||||||
local name="$3"
|
|
||||||
local msg rc total total_mb progress name
|
|
||||||
|
|
||||||
local dest="$STORAGE/$base.tmp"
|
|
||||||
rm -f "$dest"
|
|
||||||
|
|
||||||
# Check if running with interactive TTY or redirected to docker log
|
|
||||||
if [ -t 1 ]; then
|
|
||||||
progress="--progress=bar:noscroll"
|
|
||||||
else
|
|
||||||
progress="--progress=dot:giga"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$name" ]; then
|
|
||||||
name="$base"
|
|
||||||
msg="Downloading image"
|
|
||||||
else
|
|
||||||
msg="Downloading $name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Downloading $name..."
|
|
||||||
html "$msg..."
|
|
||||||
|
|
||||||
/run/progress.sh "$dest" "0" "$msg ([P])..." &
|
|
||||||
|
|
||||||
{ wget "$url" -O "$dest" -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || :
|
|
||||||
|
|
||||||
fKill "progress.sh"
|
|
||||||
|
|
||||||
if (( rc == 0 )) && [ -f "$dest" ]; then
|
|
||||||
total=$(stat -c%s "$dest")
|
|
||||||
total_gb=$(formatBytes "$total")
|
|
||||||
if [ "$total" -lt 100000 ]; then
|
|
||||||
error "Invalid image file: is only $total_gb ?" && return 1
|
|
||||||
fi
|
|
||||||
html "Download finished successfully..."
|
|
||||||
mv -f "$dest" "$STORAGE/$base"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg="Failed to download $url"
|
|
||||||
(( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
|
|
||||||
(( rc == 4 )) && error "$msg , network failure!" && return 1
|
|
||||||
(( rc == 8 )) && error "$msg , server issued an error response!" && return 1
|
|
||||||
|
|
||||||
error "$msg , reason: $rc"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
convertImage() {
|
|
||||||
|
|
||||||
local source_file=$1
|
|
||||||
local source_fmt=$2
|
|
||||||
local dst_file=$3
|
|
||||||
local dst_fmt=$4
|
|
||||||
local dir base fs fa space space_gb
|
|
||||||
local cur_size cur_gb src_size disk_param
|
|
||||||
|
|
||||||
[ -f "$dst_file" ] && error "Conversion failed, destination file $dst_file already exists?" && return 1
|
|
||||||
[ ! -f "$source_file" ] && error "Conversion failed, source file $source_file does not exists?" && return 1
|
|
||||||
|
|
||||||
if [[ "${source_fmt,,}" == "${dst_fmt,,}" ]]; then
|
|
||||||
mv -f "$source_file" "$dst_file"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local tmp_file="$dst_file.tmp"
|
|
||||||
dir=$(dirname "$tmp_file")
|
|
||||||
|
|
||||||
rm -f "$tmp_file"
|
|
||||||
|
|
||||||
if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
|
|
||||||
# Check free diskspace
|
|
||||||
src_size=$(qemu-img info "$source_file" -f "$source_fmt" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/')
|
|
||||||
space=$(df --output=avail -B 1 "$dir" | tail -n 1)
|
|
||||||
|
|
||||||
if (( src_size > space )); then
|
|
||||||
space_gb=$(formatBytes "$space")
|
|
||||||
error "Not enough free space to convert image in $dir, it has only $space_gb available..." && return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
base=$(basename "$source_file")
|
|
||||||
info "Converting $base..."
|
|
||||||
html "Converting image..."
|
|
||||||
|
|
||||||
local conv_flags="-p"
|
|
||||||
|
|
||||||
if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
disk_param="preallocation=off"
|
|
||||||
else
|
|
||||||
disk_param="preallocation=falloc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fs=$(stat -f -c %T "$dir")
|
|
||||||
[[ "${fs,,}" == "btrfs" ]] && disk_param+=",nocow=on"
|
|
||||||
|
|
||||||
if [[ "$dst_fmt" != "raw" ]]; then
|
|
||||||
if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then
|
|
||||||
conv_flags+=" -c"
|
|
||||||
fi
|
|
||||||
[ -n "${DISK_FLAGS:-}" ] && disk_param+=",$DISK_FLAGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
if ! qemu-img convert -f "$source_fmt" $conv_flags -o "$disk_param" -O "$dst_fmt" -- "$source_file" "$tmp_file"; then
|
|
||||||
rm -f "$tmp_file"
|
|
||||||
error "Failed to convert image in $dir, is there enough space available?" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$dst_fmt" == "raw" ]]; then
|
|
||||||
if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then
|
|
||||||
# Work around qemu-img bug
|
|
||||||
cur_size=$(stat -c%s "$tmp_file")
|
|
||||||
cur_gb=$(formatBytes "$cur_size")
|
|
||||||
if ! fallocate -l "$cur_size" "$tmp_file"; then
|
|
||||||
error "Failed to allocate $cur_gb for image!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$source_file"
|
|
||||||
mv "$tmp_file" "$dst_file"
|
|
||||||
|
|
||||||
if [[ "${fs,,}" == "btrfs" ]]; then
|
|
||||||
fa=$(lsattr "$dst_file")
|
|
||||||
if [[ "$fa" != *"C"* ]]; then
|
|
||||||
error "Failed to disable COW for image on ${fs^^} filesystem!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
html "Conversion completed..."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
findFile() {
|
|
||||||
|
|
||||||
local file
|
|
||||||
local ext="$1"
|
|
||||||
local fname="boot.$ext"
|
|
||||||
|
|
||||||
if [ -d "/$fname" ]; then
|
|
||||||
warn "The file /$fname has an invalid path!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
file=$(find / -maxdepth 1 -type f -iname "$fname" | head -n 1)
|
|
||||||
[ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" | head -n 1)
|
|
||||||
detectType "$file" && return 0
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
findFile "iso" && return 0
|
|
||||||
findFile "img" && return 0
|
|
||||||
findFile "raw" && return 0
|
|
||||||
findFile "qcow2" && return 0
|
|
||||||
|
|
||||||
if [ -z "$BOOT" ] || [[ "$BOOT" == *"example.com/image.iso" ]]; then
|
|
||||||
hasDisk && return 0
|
|
||||||
error "No value specified for the BOOT variable." && exit 64
|
|
||||||
fi
|
|
||||||
|
|
||||||
! getURL "$BOOT" "test" && exit 34
|
|
||||||
|
|
||||||
url=$(getURL "$BOOT" "url")
|
|
||||||
name=$(getURL "$BOOT" "name")
|
|
||||||
|
|
||||||
[ -n "$url" ] && BOOT="$url"
|
|
||||||
|
|
||||||
if [[ "$BOOT" != *"."* ]]; then
|
|
||||||
error "Invalid BOOT value specified, shortcut \"$BOOT\" is not recognized!" && exit 64
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${BOOT,,}" != "http"* ]]; then
|
|
||||||
error "Invalid BOOT value specified, \"$BOOT\" is not a valid URL!" && exit 64
|
|
||||||
fi
|
|
||||||
|
|
||||||
base=$(basename "${BOOT%%\?*}")
|
|
||||||
: "${base//+/ }"; printf -v base '%b' "${_//%/\\x}"
|
|
||||||
base=$(echo "$base" | sed -e 's/[^A-Za-z0-9._-]/_/g')
|
|
||||||
|
|
||||||
case "${base,,}" in
|
|
||||||
|
|
||||||
*".iso" | *".img" | *".raw" | *".qcow2" )
|
|
||||||
|
|
||||||
detectType "$STORAGE/$base" && return 0 ;;
|
|
||||||
|
|
||||||
*".vdi" | *".vmdk" | *".vhd" | *".vhdx" )
|
|
||||||
|
|
||||||
detectType "$STORAGE/${base%.*}.img" && return 0
|
|
||||||
detectType "$STORAGE/${base%.*}.qcow2" && return 0 ;;
|
|
||||||
|
|
||||||
*".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
|
|
||||||
|
|
||||||
case "${base%.*}" in
|
|
||||||
|
|
||||||
*".iso" | *".img" | *".raw" | *".qcow2" )
|
|
||||||
|
|
||||||
detectType "$STORAGE/${base%.*}" && return 0 ;;
|
|
||||||
|
|
||||||
*".vdi" | *".vmdk" | *".vhd" | *".vhdx" )
|
|
||||||
|
|
||||||
find="${base%.*}"
|
|
||||||
|
|
||||||
detectType "$STORAGE/${find%.*}.img" && return 0
|
|
||||||
detectType "$STORAGE/${find%.*}.qcow2" && return 0 ;;
|
|
||||||
|
|
||||||
esac ;;
|
|
||||||
|
|
||||||
* ) error "Unknown file extension, type \".${base/*./}\" is not recognized!" && exit 33 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if ! downloadFile "$BOOT" "$base" "$name"; then
|
|
||||||
rm -f "$STORAGE/$base.tmp" && exit 60
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${base,,}" in
|
|
||||||
*".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
|
|
||||||
info "Extracting $base..."
|
|
||||||
html "Extracting image..." ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${base,,}" in
|
|
||||||
*".gz" | *".gzip" )
|
|
||||||
|
|
||||||
gzip -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
|
|
||||||
rm -f "$STORAGE/$base"
|
|
||||||
base="${base%.*}"
|
|
||||||
|
|
||||||
;;
|
|
||||||
*".xz" )
|
|
||||||
|
|
||||||
xz -dc "$STORAGE/$base" > "$STORAGE/${base%.*}"
|
|
||||||
rm -f "$STORAGE/$base"
|
|
||||||
base="${base%.*}"
|
|
||||||
|
|
||||||
;;
|
|
||||||
*".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
|
|
||||||
|
|
||||||
tmp="$STORAGE/extract"
|
|
||||||
rm -rf "$tmp"
|
|
||||||
mkdir -p "$tmp"
|
|
||||||
7z x "$STORAGE/$base" -o"$tmp" > /dev/null
|
|
||||||
|
|
||||||
rm -f "$STORAGE/$base"
|
|
||||||
base="${base%.*}"
|
|
||||||
|
|
||||||
if [ ! -s "$tmp/$base" ]; then
|
|
||||||
rm -rf "$tmp"
|
|
||||||
error "Cannot find file \"${base}\" in .${BOOT/*./} archive!" && exit 32
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "$tmp/$base" "$STORAGE/$base"
|
|
||||||
rm -rf "$tmp"
|
|
||||||
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "${base,,}" in
|
|
||||||
*".iso" | *".img" | *".raw" | *".qcow2" )
|
|
||||||
detectType "$STORAGE/$base" && return 0
|
|
||||||
error "Cannot read file \"${base}\"" && exit 63 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
target_ext="img"
|
|
||||||
target_fmt="${DISK_FMT:-}"
|
|
||||||
[ -z "$target_fmt" ] && target_fmt="raw"
|
|
||||||
[[ "$target_fmt" != "raw" ]] && target_ext="qcow2"
|
|
||||||
|
|
||||||
case "${base,,}" in
|
|
||||||
*".vdi" ) source_fmt="vdi" ;;
|
|
||||||
*".vhd" ) source_fmt="vpc" ;;
|
|
||||||
*".vhdx" ) source_fmt="vpc" ;;
|
|
||||||
*".vmdk" ) source_fmt="vmdk" ;;
|
|
||||||
* ) error "Unknown file extension, type \".${base/*./}\" is not recognized!" && exit 33 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
dst="$STORAGE/${base%.*}.$target_ext"
|
|
||||||
|
|
||||||
! convertImage "$STORAGE/$base" "$source_fmt" "$dst" "$target_fmt" && exit 35
|
|
||||||
|
|
||||||
base=$(basename "$dst")
|
|
||||||
detectType "$STORAGE/$base" && return 0
|
|
||||||
error "Cannot convert file \"${base}\"" && exit 36
|
|
486
src/network.sh
486
src/network.sh
|
@ -1,486 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${MAC:=""}"
|
|
||||||
: "${MTU:=""}"
|
|
||||||
: "${DHCP:="N"}"
|
|
||||||
: "${NETWORK:="Y"}"
|
|
||||||
: "${USER_PORTS:=""}"
|
|
||||||
: "${HOST_PORTS:=""}"
|
|
||||||
: "${ADAPTER:="virtio-net-pci"}"
|
|
||||||
|
|
||||||
: "${VM_NET_DEV:=""}"
|
|
||||||
: "${VM_NET_TAP:="qemu"}"
|
|
||||||
: "${VM_NET_MAC:="$MAC"}"
|
|
||||||
: "${VM_NET_HOST:="QEMU"}"
|
|
||||||
: "${VM_NET_IP:="20.20.20.21"}"
|
|
||||||
|
|
||||||
: "${DNSMASQ_OPTS:=""}"
|
|
||||||
: "${DNSMASQ:="/usr/sbin/dnsmasq"}"
|
|
||||||
: "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
|
|
||||||
|
|
||||||
ADD_ERR="Please add the following setting to your container:"
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Functions
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
configureDHCP() {
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/vhost-net
|
|
||||||
if [ ! -c /dev/vhost-net ]; then
|
|
||||||
if mknod /dev/vhost-net c 10 238; then
|
|
||||||
chmod 660 /dev/vhost-net
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a macvtap network for the VM guest
|
|
||||||
{ msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1); rc=$?; } || :
|
|
||||||
|
|
||||||
case "$msg" in
|
|
||||||
"RTNETLINK answers: File exists"* )
|
|
||||||
while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do
|
|
||||||
info "Waiting for macvtap interface to become available.."
|
|
||||||
sleep 5
|
|
||||||
done ;;
|
|
||||||
"RTNETLINK answers: Invalid argument"* )
|
|
||||||
error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'."
|
|
||||||
return 1 ;;
|
|
||||||
"RTNETLINK answers: Operation not permitted"* )
|
|
||||||
error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set."
|
|
||||||
return 1 ;;
|
|
||||||
*)
|
|
||||||
[ -n "$msg" ] && echo "$msg" >&2
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create macvtap interface."
|
|
||||||
return 1
|
|
||||||
fi ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ "$MTU" != "0" ]] && [[ "$MTU" != "1500" ]]; then
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
|
|
||||||
warn "Failed to set MTU size.."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "$VM_NET_TAP" up; do
|
|
||||||
info "Waiting for MAC address $VM_NET_MAC to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
local TAP_NR TAP_PATH MAJOR MINOR
|
|
||||||
TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
|
|
||||||
TAP_PATH="/dev/tap${TAP_NR}"
|
|
||||||
|
|
||||||
# Create dev file (there is no udev in container: need to be done manually)
|
|
||||||
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
|
|
||||||
(( MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
|
|
||||||
|
|
||||||
[[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
|
|
||||||
|
|
||||||
if [[ ! -e "$TAP_PATH" ]]; then
|
|
||||||
{ mknod "$TAP_PATH" c "$MAJOR" "$MINOR" ; rc=$?; } || :
|
|
||||||
(( rc != 0 )) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 30>>"$TAP_PATH"; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureDNS() {
|
|
||||||
|
|
||||||
# dnsmasq configuration:
|
|
||||||
DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0"
|
|
||||||
|
|
||||||
# Create lease file for faster resolve
|
|
||||||
echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" > /var/lib/misc/dnsmasq.leases
|
|
||||||
chmod 644 /var/lib/misc/dnsmasq.leases
|
|
||||||
|
|
||||||
# Set DNS server and gateway
|
|
||||||
DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1"
|
|
||||||
|
|
||||||
# Add DNS entry for container
|
|
||||||
DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP%.*}.1"
|
|
||||||
|
|
||||||
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
|
||||||
|
|
||||||
if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
|
|
||||||
error "Failed to start dnsmasq, reason: $?" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserPorts() {
|
|
||||||
|
|
||||||
local args=""
|
|
||||||
local list=$1
|
|
||||||
local ssh="22"
|
|
||||||
local rdp="3389"
|
|
||||||
|
|
||||||
[ -z "$list" ] && list="$ssh,$rdp" || list+=",$ssh,$rdp"
|
|
||||||
|
|
||||||
list="${list//,/ }"
|
|
||||||
list="${list## }"
|
|
||||||
list="${list%% }"
|
|
||||||
|
|
||||||
for port in $list; do
|
|
||||||
args+="hostfwd=tcp::$port-$VM_NET_IP:$port,"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "${args%?}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getHostPorts() {
|
|
||||||
|
|
||||||
local list=$1
|
|
||||||
local vnc="5900"
|
|
||||||
local web="8006"
|
|
||||||
|
|
||||||
[ -z "$list" ] && list="$web" || list+=",$web"
|
|
||||||
|
|
||||||
if [[ "${DISPLAY,,}" == "vnc" ]] || [[ "${DISPLAY,,}" == "web" ]]; then
|
|
||||||
[ -z "$list" ] && list="$vnc" || list+=",$vnc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -z "$list" ] && echo "" && return 0
|
|
||||||
|
|
||||||
if [[ "$list" != *","* ]]; then
|
|
||||||
echo " ! --dport $list"
|
|
||||||
else
|
|
||||||
echo " -m multiport ! --dports $list"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureUser() {
|
|
||||||
|
|
||||||
NET_OPTS="-netdev user,id=hostnet0,host=${VM_NET_IP%.*}.1,net=${VM_NET_IP%.*}.0/24,dhcpstart=$VM_NET_IP,hostname=$VM_NET_HOST"
|
|
||||||
|
|
||||||
local forward
|
|
||||||
forward=$(getUserPorts "$USER_PORTS")
|
|
||||||
[ -n "$forward" ] && NET_OPTS+=",$forward"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
configureNAT() {
|
|
||||||
|
|
||||||
local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
|
|
||||||
local tables="The 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
|
|
||||||
|
|
||||||
# Create the necessary file structure for /dev/net/tun
|
|
||||||
if [ ! -c /dev/net/tun ]; then
|
|
||||||
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
|
||||||
if mknod /dev/net/tun c 10 200; then
|
|
||||||
chmod 666 /dev/net/tun
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -c /dev/net/tun ]; then
|
|
||||||
error "$tuntap" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check port forwarding flag
|
|
||||||
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
|
||||||
{ sysctl -w net.ipv4.ip_forward=1 > /dev/null; rc=$?; } || :
|
|
||||||
if (( rc != 0 )) || [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
|
||||||
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a bridge with a static IP for the VM guest
|
|
||||||
{ ip link add dev dockerbridge type bridge ; rc=$?; } || :
|
|
||||||
|
|
||||||
if (( rc != 0 )); then
|
|
||||||
error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ip address add "${VM_NET_IP%.*}.1/24" broadcast "${VM_NET_IP%.*}.255" dev dockerbridge; then
|
|
||||||
error "Failed to add IP address!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set dockerbridge up; do
|
|
||||||
info "Waiting for IP address to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
# QEMU Works with taps, set tap to the bridge created
|
|
||||||
if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
|
|
||||||
error "$tuntap" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$MTU" != "0" ]] && [[ "$MTU" != "1500" ]]; then
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" mtu "$MTU"; then
|
|
||||||
warn "Failed to set MTU size.."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
GATEWAY_MAC=$(echo "$VM_NET_MAC" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
|
|
||||||
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" address "$GATEWAY_MAC"; then
|
|
||||||
warn "Failed to set gateway MAC address.."
|
|
||||||
fi
|
|
||||||
|
|
||||||
while ! ip link set "$VM_NET_TAP" up promisc on; do
|
|
||||||
info "Waiting for TAP to become available..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! ip link set dev "$VM_NET_TAP" master dockerbridge; then
|
|
||||||
error "Failed to set IP link!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add internet connection to the VM
|
|
||||||
update-alternatives --set iptables /usr/sbin/iptables-legacy > /dev/null
|
|
||||||
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy > /dev/null
|
|
||||||
|
|
||||||
exclude=$(getHostPorts "$HOST_PORTS")
|
|
||||||
|
|
||||||
if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then
|
|
||||||
error "$tables" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP"; then
|
|
||||||
error "Failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"; then
|
|
||||||
error "Failed to configure IP tables!" && return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( KERNEL > 4 )); then
|
|
||||||
# Hack for guest VMs complaining about "bad udp checksums in 5 packets"
|
|
||||||
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
|
|
||||||
|
|
||||||
if [ -c /dev/vhost-net ]; then
|
|
||||||
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
|
|
||||||
(( rc == 0 )) && NET_OPTS+=",vhost=on,vhostfd=40"
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS+=",script=no,downscript=no"
|
|
||||||
|
|
||||||
configureDNS || return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeBridge() {
|
|
||||||
|
|
||||||
local pid="/var/run/dnsmasq.pid"
|
|
||||||
[ -s "$pid" ] && pKill "$(<"$pid")"
|
|
||||||
|
|
||||||
[[ "${NETWORK,,}" == "user"* ]] && return 0
|
|
||||||
|
|
||||||
ip link set "$VM_NET_TAP" down promisc off &> null || true
|
|
||||||
ip link delete "$VM_NET_TAP" &> null || true
|
|
||||||
|
|
||||||
ip link set dockerbridge down &> null || true
|
|
||||||
ip link delete dockerbridge &> null || true
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
closeNetwork() {
|
|
||||||
|
|
||||||
# Shutdown nginx
|
|
||||||
nginx -s stop 2> /dev/null
|
|
||||||
fWait "nginx"
|
|
||||||
|
|
||||||
[[ "$NETWORK" == [Nn]* ]] && return 0
|
|
||||||
|
|
||||||
exec 30<&- || true
|
|
||||||
exec 40<&- || true
|
|
||||||
|
|
||||||
if [[ "$DHCP" != [Yy1]* ]]; then
|
|
||||||
|
|
||||||
closeBridge
|
|
||||||
return 0
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
ip link set "$VM_NET_TAP" down || true
|
|
||||||
ip link delete "$VM_NET_TAP" || true
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
checkOS() {
|
|
||||||
|
|
||||||
local name
|
|
||||||
local os=""
|
|
||||||
local if="macvlan"
|
|
||||||
name=$(uname -a)
|
|
||||||
|
|
||||||
[[ "${name,,}" == *"darwin"* ]] && os="Docker Desktop for macOS"
|
|
||||||
[[ "${name,,}" == *"microsoft"* ]] && os="Docker Desktop for Windows"
|
|
||||||
|
|
||||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
|
||||||
if="macvtap"
|
|
||||||
[[ "${name,,}" == *"synology"* ]] && os="Synology Container Manager"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$os" ]; then
|
|
||||||
warn "you are using $os which does not support $if, please revert to bridge networking!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo() {
|
|
||||||
|
|
||||||
if [ -z "$VM_NET_DEV" ]; then
|
|
||||||
# Give Kubernetes priority over the default interface
|
|
||||||
[ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
|
|
||||||
[ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
|
|
||||||
[ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
|
|
||||||
[ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
|
|
||||||
# Automaticly detect the default network interface
|
|
||||||
[ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
|
|
||||||
[ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
|
|
||||||
error "Network interface '$VM_NET_DEV' does not exist inside the container!"
|
|
||||||
error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$MTU" ]; then
|
|
||||||
MTU=$(cat "/sys/class/net/$VM_NET_DEV/mtu")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$MTU" -gt "1500" ]; then
|
|
||||||
info "MTU size is too large: $MTU, ignoring..." && MTU="0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${ADAPTER,,}" != "virtio-net-pci" ]]; then
|
|
||||||
if [[ "$MTU" != "0" ]] && [[ "$MTU" != "1500" ]]; then
|
|
||||||
warn "MTU size is $MTU, but cannot be set for $ADAPTER adapters!" && MTU="0"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
|
|
||||||
if [[ "$MTU" != "0" ]] && [[ "$MTU" != "1500" ]]; then
|
|
||||||
warn "MTU size is $MTU, but cannot be set for legacy Windows versions!" && MTU="0"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$MAC" ]; then
|
|
||||||
local file="$STORAGE/$PROCESS.mac"
|
|
||||||
[ -s "$file" ] && MAC=$(<"$file")
|
|
||||||
if [ -z "$MAC" ]; then
|
|
||||||
# Generate MAC address based on Docker container ID in hostname
|
|
||||||
MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
|
|
||||||
echo "${MAC^^}" > "$file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
VM_NET_MAC="${MAC^^}"
|
|
||||||
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
|
||||||
|
|
||||||
if [[ ${#VM_NET_MAC} == 12 ]]; then
|
|
||||||
m="$VM_NET_MAC"
|
|
||||||
VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#VM_NET_MAC} != 17 ]]; then
|
|
||||||
error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
|
|
||||||
fi
|
|
||||||
|
|
||||||
GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}')
|
|
||||||
IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# ######################################
|
|
||||||
# Configure Network
|
|
||||||
# ######################################
|
|
||||||
|
|
||||||
if [[ "$NETWORK" == [Nn]* ]]; then
|
|
||||||
NET_OPTS=""
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
getInfo
|
|
||||||
html "Initializing network..."
|
|
||||||
|
|
||||||
if [[ "$DEBUG" == [Yy1]* ]]; then
|
|
||||||
mtu=$(cat "/sys/class/net/$VM_NET_DEV/mtu")
|
|
||||||
line="Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC MTU: $mtu"
|
|
||||||
[[ "$MTU" != "0" ]] && [[ "$MTU" != "$mtu" ]] && line+=" ($MTU)"
|
|
||||||
info "$line"
|
|
||||||
[ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "/sys/class/net/$VM_NET_TAP" ]]; then
|
|
||||||
info "Lingering interface will be removed..."
|
|
||||||
ip link delete "$VM_NET_TAP" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$DHCP" == [Yy1]* ]]; then
|
|
||||||
|
|
||||||
checkOS
|
|
||||||
|
|
||||||
if [[ "$IP" == "172."* ]]; then
|
|
||||||
warn "container IP starts with 172.* which is often a sign that you are not on a macvlan network (required for DHCP)!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure for macvtap interface
|
|
||||||
configureDHCP || exit 20
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then
|
|
||||||
checkOS
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${NETWORK,,}" != "user"* ]]; then
|
|
||||||
|
|
||||||
# Configure for tap interface
|
|
||||||
if ! configureNAT; then
|
|
||||||
|
|
||||||
closeBridge
|
|
||||||
NETWORK="user"
|
|
||||||
warn "falling back to usermode networking! Performance will be bad and port mapping will not work."
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${NETWORK,,}" == "user"* ]]; then
|
|
||||||
|
|
||||||
# Configure for usermode networking (slirp)
|
|
||||||
configureUser || exit 24
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
NET_OPTS+=" -device $ADAPTER,id=net0,netdev=hostnet0,romfile=,mac=$VM_NET_MAC"
|
|
||||||
[[ "$MTU" != "0" ]] && [[ "$MTU" != "1500" ]] && NET_OPTS+=",host_mtu=$MTU"
|
|
||||||
|
|
||||||
html "Initialized network successfully..."
|
|
||||||
return 0
|
|
18
src/proc.sh
18
src/proc.sh
|
@ -34,10 +34,10 @@ if [[ "$KVM" != [Nn]* ]]; then
|
||||||
KVM_ERR=""
|
KVM_ERR=""
|
||||||
|
|
||||||
if [ ! -e /dev/kvm ]; then
|
if [ ! -e /dev/kvm ]; then
|
||||||
KVM_ERR="(device file missing)"
|
KVM_ERR="(/dev/kvm is missing)"
|
||||||
else
|
else
|
||||||
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
if ! sh -c 'echo -n > /dev/kvm' &> /dev/null; then
|
||||||
KVM_ERR="(no write access)"
|
KVM_ERR="(/dev/kvm is unwriteable)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -45,9 +45,17 @@ if [[ "$KVM" != [Nn]* ]]; then
|
||||||
KVM="N"
|
KVM="N"
|
||||||
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
if [[ "$OSTYPE" =~ ^darwin ]]; then
|
||||||
warn "you are using macOS which has no KVM support, this will cause a major loss of performance."
|
warn "you are using macOS which has no KVM support, this will cause a major loss of performance."
|
||||||
else
|
else
|
||||||
error "KVM acceleration not available $KVM_ERR, this will cause a major loss of performance."
|
kernel=$(uname -a)
|
||||||
error "See the FAQ on how to diagnose the cause, or continue without KVM by setting KVM=N (not recommended)."
|
case "${kernel,,}" in
|
||||||
|
*"microsoft"* )
|
||||||
|
error "Please bind '/dev/kvm' as a volume in the optional container settings when using Docker Desktop." ;;
|
||||||
|
*"synology"* )
|
||||||
|
error "Please make sure that Synology VMM (Virtual Machine Manager) is installed and that '/dev/kvm' is binded to this container." ;;
|
||||||
|
*)
|
||||||
|
error "KVM acceleration is not available $KVM_ERR, this will cause a major loss of performance."
|
||||||
|
error "See the FAQ for possible causes, or continue without it by adding KVM: \"N\" (not recommended)." ;;
|
||||||
|
esac
|
||||||
[[ "$DEBUG" != [Yy1]* ]] && exit 88
|
[[ "$DEBUG" != [Yy1]* ]] && exit 88
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
local s
|
|
||||||
s=${1//&/\&}
|
|
||||||
s=${s//</\<}
|
|
||||||
s=${s//>/\>}
|
|
||||||
s=${s//'"'/\"}
|
|
||||||
printf -- %s "$s"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
file="$1"
|
|
||||||
total="$2"
|
|
||||||
body=$(escape "$3")
|
|
||||||
info="/run/shm/msg.html"
|
|
||||||
|
|
||||||
if [[ "$body" == *"..." ]]; then
|
|
||||||
body="<p class=\"loading\">${body/.../}</p>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
while true
|
|
||||||
do
|
|
||||||
if [ -s "$file" ]; then
|
|
||||||
bytes=$(du -sb "$file" | cut -f1)
|
|
||||||
if (( bytes > 1000 )); then
|
|
||||||
if [ -z "$total" ] || [[ "$total" == "0" ]]; then
|
|
||||||
size=$(numfmt --to=iec --suffix=B "$bytes" | sed -r 's/([A-Z])/ \1/')
|
|
||||||
else
|
|
||||||
size="$(echo "$bytes" "$total" | awk '{printf "%.1f", $1 * 100 / $2}')"
|
|
||||||
size="$size%"
|
|
||||||
fi
|
|
||||||
echo "${body//(\[P\])/($size)}"> "$info"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
sleep 1 & wait $!
|
|
||||||
done
|
|
313
src/reset.sh
313
src/reset.sh
|
@ -1,313 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
info () { printf "%b%s%b" "\E[1;34m❯ \E[1;36m" "${1:-}" "\E[0m\n"; }
|
|
||||||
error () { printf "%b%s%b" "\E[1;31m❯ " "ERROR: ${1:-}" "\E[0m\n" >&2; }
|
|
||||||
warn () { printf "%b%s%b" "\E[1;31m❯ " "Warning: ${1:-}" "\E[0m\n" >&2; }
|
|
||||||
|
|
||||||
trap 'error "Status $? while: $BASH_COMMAND (line $LINENO/$BASH_LINENO)"' ERR
|
|
||||||
|
|
||||||
[ ! -f "/run/entry.sh" ] && error "Script must run inside Docker container!" && exit 11
|
|
||||||
[ "$(id -u)" -ne "0" ] && error "Script must be executed with root privileges." && exit 12
|
|
||||||
|
|
||||||
echo "❯ Starting $APP for Docker v$(</run/version)..."
|
|
||||||
echo "❯ For support visit $SUPPORT"
|
|
||||||
|
|
||||||
# Docker environment variables
|
|
||||||
|
|
||||||
: "${BOOT:=""}" # URL of the ISO file
|
|
||||||
: "${DEBUG:="N"}" # Disable debugging
|
|
||||||
: "${MACHINE:="virt"}" # Machine selection
|
|
||||||
: "${ALLOCATE:=""}" # Preallocate diskspace
|
|
||||||
: "${ARGUMENTS:=""}" # Extra QEMU parameters
|
|
||||||
: "${CPU_CORES:="1"}" # Amount of CPU cores
|
|
||||||
: "${RAM_SIZE:="1G"}" # Maximum RAM amount
|
|
||||||
: "${RAM_CHECK:="Y"}" # Check available RAM
|
|
||||||
: "${DISK_SIZE:="16G"}" # Initial data disk size
|
|
||||||
: "${BOOT_MODE:=""}" # Boot system with UEFI
|
|
||||||
: "${BOOT_INDEX:="9"}" # Boot index of CD drive
|
|
||||||
: "${STORAGE:="/storage"}" # Storage folder location
|
|
||||||
|
|
||||||
# Helper variables
|
|
||||||
|
|
||||||
PROCESS="${APP,,}"
|
|
||||||
PROCESS="${PROCESS// /-}"
|
|
||||||
|
|
||||||
INFO="/run/shm/msg.html"
|
|
||||||
PAGE="/run/shm/index.html"
|
|
||||||
TEMPLATE="/var/www/index.html"
|
|
||||||
FOOTER1="$APP for Docker v$(</run/version)"
|
|
||||||
FOOTER2="<a href='$SUPPORT'>$SUPPORT</a>"
|
|
||||||
|
|
||||||
CPI=$(lscpu)
|
|
||||||
SYS=$(uname -r)
|
|
||||||
HOST=$(hostname -s)
|
|
||||||
KERNEL=$(echo "$SYS" | cut -b 1)
|
|
||||||
MINOR=$(echo "$SYS" | cut -d '.' -f2)
|
|
||||||
ARCH=$(dpkg --print-architecture)
|
|
||||||
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
|
||||||
|
|
||||||
if ! grep -qi "socket(s)" <<< "$CPI"; then
|
|
||||||
SOCKETS=1
|
|
||||||
else
|
|
||||||
SOCKETS=$(echo "$CPI" | grep -m 1 -i 'socket(s)' | awk '{print $(2)}')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -qi "model name" <<< "$CPI"; then
|
|
||||||
CPU=""
|
|
||||||
else
|
|
||||||
CPU=$(echo "$CPI" | 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')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${CPU// /}" ] && grep -qi "model:" <<< "$CPI"; then
|
|
||||||
CPU=$(echo "$CPI" | 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')
|
|
||||||
fi
|
|
||||||
|
|
||||||
CPU="${CPU// CPU/}"
|
|
||||||
CPU="${CPU// 8 Core/}"
|
|
||||||
CPU="${CPU// 16 Core/}"
|
|
||||||
CPU="${CPU// 32 Core/}"
|
|
||||||
CPU="${CPU// 64 Core/}"
|
|
||||||
CPU="${CPU// Processor/}"
|
|
||||||
CPU="${CPU// Quad core/}"
|
|
||||||
CPU="${CPU// Core TM/ Core}"
|
|
||||||
CPU="${CPU// with Radeon Graphics/}"
|
|
||||||
CPU="${CPU// with Radeon Vega Graphics/}"
|
|
||||||
|
|
||||||
[ -z "${CPU// /}" ] && CPU="Unknown"
|
|
||||||
[ -n "${CPU_CORES//[0-9 ]}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
|
||||||
|
|
||||||
# Check system
|
|
||||||
|
|
||||||
if [ ! -d "/dev/shm" ]; then
|
|
||||||
error "Directory /dev/shm not found!" && exit 14
|
|
||||||
else
|
|
||||||
[ ! -d "/run/shm" ] && ln -s /dev/shm /run/shm
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check folder
|
|
||||||
|
|
||||||
if [[ "${COMMIT:-}" == [Yy1]* ]]; then
|
|
||||||
STORAGE="/local"
|
|
||||||
mkdir -p "$STORAGE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "$STORAGE" ]; then
|
|
||||||
error "Storage folder ($STORAGE) not found!" && exit 13
|
|
||||||
fi
|
|
||||||
|
|
||||||
formatBytes() {
|
|
||||||
local result
|
|
||||||
result=$(numfmt --to=iec "$1")
|
|
||||||
local unit="${result//[0-9. ]}"
|
|
||||||
if [ -z "$unit" ]; then
|
|
||||||
unit="bytes"
|
|
||||||
else
|
|
||||||
unit=$(echo "${unit^^}" | sed 's/K/KB/g;s/M/MB/g;s/G/GB/g;s/T/TB/g')
|
|
||||||
fi
|
|
||||||
result="${result//[a-zA-Z ]/}"
|
|
||||||
if [[ "${2:-}" == "up" ]]; then
|
|
||||||
if [[ "$result" == *"."* ]]; then
|
|
||||||
result="${result%%.*}"
|
|
||||||
result=$((result+1))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ "${2:-}" == "down" ]]; then
|
|
||||||
result="${result%%.*}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo "$result $unit"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read memory
|
|
||||||
RAM_SPARE=500000000
|
|
||||||
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
|
|
||||||
RAM_TOTAL=$(free -b | grep -m 1 Mem: | awk '{print $2}')
|
|
||||||
|
|
||||||
RAM_SIZE="${RAM_SIZE// /}"
|
|
||||||
[ -z "$RAM_SIZE" ] && error "RAM_SIZE not specified!" && exit 16
|
|
||||||
|
|
||||||
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
|
||||||
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
|
||||||
fi
|
|
||||||
|
|
||||||
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
|
||||||
! numfmt --from=iec "$RAM_SIZE" &>/dev/null && error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16
|
|
||||||
RAM_WANTED=$(numfmt --from=iec "$RAM_SIZE")
|
|
||||||
[ "$RAM_WANTED" -lt "136314880 " ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
|
||||||
|
|
||||||
# Print system info
|
|
||||||
SYS="${SYS/-generic/}"
|
|
||||||
FS=$(stat -f -c %T "$STORAGE")
|
|
||||||
FS="${FS/UNKNOWN //}"
|
|
||||||
FS="${FS/ext2\/ext3/ext4}"
|
|
||||||
FS=$(echo "$FS" | sed 's/[)(]//g')
|
|
||||||
SPACE=$(df --output=avail -B 1 "$STORAGE" | tail -n 1)
|
|
||||||
SPACE_GB=$(formatBytes "$SPACE" "down")
|
|
||||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL" "down")
|
|
||||||
TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
|
||||||
|
|
||||||
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}..."
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Check compatibilty
|
|
||||||
|
|
||||||
if [[ "${FS,,}" == "ecryptfs" ]] || [[ "${FS,,}" == "tmpfs" ]]; then
|
|
||||||
DISK_IO="threads"
|
|
||||||
DISK_CACHE="writeback"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${BOOT_MODE:-}" == "windows"* ]]; then
|
|
||||||
if [[ "${FS,,}" == "btrfs" ]]; then
|
|
||||||
warn "you are using the BTRFS filesystem for /storage, this might introduce issues with Windows Setup!"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check available memory
|
|
||||||
|
|
||||||
if [[ "$RAM_CHECK" != [Nn]* ]] && (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
|
|
||||||
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
|
|
||||||
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."
|
|
||||||
[[ "${FS,,}" != "zfs" ]] && error "$msg" && exit 17
|
|
||||||
info "$msg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
|
|
||||||
isAlive() {
|
|
||||||
local pid="$1"
|
|
||||||
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pKill() {
|
|
||||||
local pid="$1"
|
|
||||||
|
|
||||||
{ kill -15 "$pid" || true; } 2>/dev/null
|
|
||||||
|
|
||||||
while isAlive "$pid"; do
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fWait() {
|
|
||||||
local name="$1"
|
|
||||||
|
|
||||||
while pgrep -f -l "$name" >/dev/null; do
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fKill() {
|
|
||||||
local name="$1"
|
|
||||||
|
|
||||||
{ pkill -f "$name" || true; } 2>/dev/null
|
|
||||||
fWait "$name"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
local s
|
|
||||||
s=${1//&/\&}
|
|
||||||
s=${s//</\<}
|
|
||||||
s=${s//>/\>}
|
|
||||||
s=${s//'"'/\"}
|
|
||||||
printf -- %s "$s"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
html()
|
|
||||||
{
|
|
||||||
local title
|
|
||||||
local body
|
|
||||||
local script
|
|
||||||
local footer
|
|
||||||
|
|
||||||
title=$(escape "$APP")
|
|
||||||
title="<title>$title</title>"
|
|
||||||
footer=$(escape "$FOOTER1")
|
|
||||||
|
|
||||||
body=$(escape "$1")
|
|
||||||
if [[ "$body" == *"..." ]]; then
|
|
||||||
body="<p class=\"loading\">${body/.../}</p>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "${2:-}" ] && script="$2" || script=""
|
|
||||||
|
|
||||||
local HTML
|
|
||||||
HTML=$(<"$TEMPLATE")
|
|
||||||
HTML="${HTML/\[1\]/$title}"
|
|
||||||
HTML="${HTML/\[2\]/$script}"
|
|
||||||
HTML="${HTML/\[3\]/$body}"
|
|
||||||
HTML="${HTML/\[4\]/$footer}"
|
|
||||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
|
||||||
|
|
||||||
echo "$HTML" > "$PAGE"
|
|
||||||
echo "$body" > "$INFO"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addPackage() {
|
|
||||||
local pkg=$1
|
|
||||||
local desc=$2
|
|
||||||
|
|
||||||
if apt-mark showinstall | grep -qx "$pkg"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MSG="Installing $desc..."
|
|
||||||
info "$MSG" && html "$MSG"
|
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -qq update
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -qq --no-install-recommends -y install "$pkg" > /dev/null
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDisk() {
|
|
||||||
|
|
||||||
[ -b "/disk" ] && return 0
|
|
||||||
[ -b "/disk1" ] && return 0
|
|
||||||
[ -b "/dev/disk1" ] && return 0
|
|
||||||
[ -b "${DEVICE:-}" ] && return 0
|
|
||||||
|
|
||||||
[ -z "${DISK_NAME:-}" ] && DISK_NAME="data"
|
|
||||||
[ -s "$STORAGE/$DISK_NAME.img" ] && return 0
|
|
||||||
[ -s "$STORAGE/$DISK_NAME.qcow2" ] && return 0
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
user="admin"
|
|
||||||
[ -n "${USER:-}" ] && user="${USER:-}"
|
|
||||||
|
|
||||||
if [ -n "${PASS:-}" ]; then
|
|
||||||
|
|
||||||
sed -i "s/auth_basic off/auth_basic \"NoVNC\"/g" /etc/nginx/sites-enabled/web.conf
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
sed -i "s/auth_basic \"NoVNC\"/auth_basic off/g" /etc/nginx/sites-enabled/web.conf
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set password
|
|
||||||
echo "$user:{PLAIN}${PASS:-}" > /etc/nginx/.htpasswd
|
|
||||||
|
|
||||||
# Start webserver
|
|
||||||
cp -r /var/www/* /run/shm
|
|
||||||
html "Starting $APP for Docker..."
|
|
||||||
nginx -e stderr
|
|
||||||
|
|
||||||
return 0
|
|
Loading…
Reference in a new issue