Compare commits

..

No commits in common. "master" and "v1.00" have entirely different histories.

24 changed files with 1241 additions and 753 deletions

View file

@ -1,6 +0,0 @@
{
"name": "qemu",
"service": "qemu",
"forwardPorts": [8006],
"dockerComposeFile": "compose.yml"
}

View file

@ -6,10 +6,7 @@
.gitmodules .gitmodules
Dockerfile Dockerfile
Dockerfile.archive Dockerfile.archive
compose.yml
compose.yaml
docker-compose.yml docker-compose.yml
docker-compose.yaml
*.md *.md

View file

@ -1,41 +0,0 @@
name: "\U0001F6A8 Technical issue"
description: When you're experiencing problems using the container
body:
- type: input
id: os
attributes:
label: Operating system
description: Your Linux distribution (can be shown by `lsb_release -a`).
placeholder: e.g. Ubuntu 24.04
validations:
required: true
- type: textarea
id: summary
attributes:
label: Description
description: A clear and concise description of your issue.
validations:
required: true
- type: textarea
id: compose
attributes:
label: Docker compose
description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations:
required: true
- type: textarea
id: log
attributes:
label: Docker log
description: The logfile of the container (as shown by `docker logs qemu`).
render: shell
validations:
required: true
- type: textarea
id: screenshot
attributes:
label: Screenshots (optional)
description: Screenshots that might help to make the problem more clear.
validations:
required: false

View file

@ -1,37 +0,0 @@
name: "\U0001F680 Feature request"
description: Suggest an idea for improving the container
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: Is your proposal related to a problem?
description: |
Provide a clear and concise description of what the problem is.
For example, "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like.
description: |
Provide a clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered.
description: |
Let us know about other solutions you've tried or researched.
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: |
Is there anything else you can add about the proposal?
You might want to link to related issues here, if you haven't already.

View file

@ -1,43 +0,0 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve the container
title: "[Bug]: "
labels: ["bug"]
body:
- type: input
id: os
attributes:
label: Operating system
description: Your Linux distribution (can be shown by `lsb_release -a`).
placeholder: e.g. Ubuntu 24.04
validations:
required: true
- type: textarea
id: summary
attributes:
label: Description
description: Describe the expected behaviour, the actual behaviour, and the steps to reproduce.
validations:
required: true
- type: textarea
id: compose
attributes:
label: Docker compose
description: The compose file (or otherwise the `docker run` command used).
render: yaml
validations:
required: true
- type: textarea
id: log
attributes:
label: Docker log
description: The logfile of the container (as shown by `docker logs qemu`).
render: shell
validations:
required: true
- type: textarea
id: screenshot
attributes:
label: Screenshots (optional)
description: Screenshots that might help to make the problem more clear.
validations:
required: false

View file

@ -1,26 +0,0 @@
name: "\U00002753 General question"
description: Questions about the container not related to an issue
title: "[Question]: "
labels: ["question"]
body:
- type: checkboxes
attributes:
label: Is your question not already answered in the FAQ?
description: Please read the [FAQ](https://github.com/qemus/qemu-arm/blob/master/readme.md) carefully to avoid asking duplicate questions.
options:
- label: I made sure the question is not listed in the [FAQ](https://github.com/qemus/qemu-arm/blob/master/readme.md).
required: true
- type: checkboxes
attributes:
label: Is this a general question and not a technical issue?
description: For questions related to issues you must use the [technical issue](https://github.com/qemus/qemu-arm/issues/new?assignees=&labels=&projects=&template=1-issue.yml) form instead. It contains all the right fields (system info, logfiles, etc.) we need in order to be able to help you.
options:
- label: I am sure my question is not about a technical issue.
required: true
- type: textarea
id: question
attributes:
label: Question
description: What's the question you have about the container?
validations:
required: true

View file

@ -1 +0,0 @@
blank_issues_enabled: false

View file

@ -46,8 +46,8 @@ jobs:
with: with:
context: git context: git
images: | images: |
ghcr.io/${{ github.repository }}
${{ secrets.DOCKERHUB_REPO }} ${{ secrets.DOCKERHUB_REPO }}
ghcr.io/${{ github.repository }}
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 }}
@ -73,7 +73,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- -
name: Build Docker image name: Build Docker image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
push: true push: true
@ -96,22 +96,3 @@ jobs:
uses: action-pack/bump@v2 uses: action-pack/bump@v2
with: with:
token: ${{ secrets.REPO_ACCESS_TOKEN }} token: ${{ secrets.REPO_ACCESS_TOKEN }}
-
name: Push to Gitlab mirror
uses: action-pack/gitlab-sync@v3
with:
url: ${{ secrets.GITLAB_URL }}
token: ${{ secrets.GITLAB_TOKEN }}
username: ${{ secrets.GITLAB_USERNAME }}
-
name: Send mail
uses: action-pack/send-mail@v1
with:
to: ${{secrets.MAILTO}}
from: Github Actions <${{secrets.MAILTO}}>
connection_url: ${{secrets.MAIL_CONNECTION}}
subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed
body: |
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.

View file

@ -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 SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 SHELLCHECK_OPTS: -x --source-path=src -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

View file

@ -1,25 +1,13 @@
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 DEBCONF_NOWARNINGS "yes"
ARG VERSION_VNC="1.6.0" ARG DEBIAN_FRONTEND "noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN "true"
ARG DEBCONF_NOWARNINGS="yes" RUN apt-get update \
ARG DEBIAN_FRONTEND="noninteractive" && apt-get --no-install-recommends -y install \
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \
apt-get update && \
apt-get --no-install-recommends -y install \
bc \
jq \
tini \ tini \
wget \ wget \
7zip \
curl \
fdisk \
nginx \ nginx \
procps \ procps \
seabios \ seabios \
@ -27,42 +15,41 @@ RUN set -eu && \
iproute2 \ iproute2 \
apt-utils \ apt-utils \
dnsmasq \ dnsmasq \
xz-utils \
net-tools \ net-tools \
e2fsprogs \
qemu-utils \ qemu-utils \
iputils-ping \
genisoimage \
ca-certificates \ ca-certificates \
qemu-system-arm && \ netcat-openbsd \
apt-get clean && \ qemu-system-arm \
mkdir -p /etc/qemu && \ qemu-efi-aarch64 \
echo "allow br0" > /etc/qemu/bridge.conf && \ && apt-get clean \
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 && \ && novnc="1.4.0" \
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"$novnc".tar.gz -O /tmp/novnc.tar.gz -q \
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/ && \ && cd /tmp/noVNC-"$novnc" \
cd "/tmp/noVNC-${VERSION_VNC}" && \ && mv app core vendor package.json *.html /usr/share/novnc \
mv app core vendor package.json *.html /usr/share/novnc && \ && unlink /etc/nginx/sites-enabled/default \
unlink /etc/nginx/sites-enabled/default && \ && sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf \
sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
echo "$VERSION_ARG" > /run/version && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=src /run/*.sh /run ADD https://raw.githubusercontent.com/qemus/qemu-docker/master/web/index.html /var/www/index.html
COPY --from=src /var/www /var/www ADD https://raw.githubusercontent.com/qemus/qemu-docker/master/web/js/script.js /var/www/js/script.js
COPY --from=src /usr/share/novnc /usr/share/novnc ADD https://raw.githubusercontent.com/qemus/qemu-docker/master/web/css/style.css /var/www/css/style.css
COPY --from=src /etc/nginx/sites-enabled /etc/nginx/sites-enabled ADD https://raw.githubusercontent.com/qemus/qemu-docker/master/web/img/favicon.svg /var/www/img/favicon.svg
ADD https://raw.githubusercontent.com/qemus/qemu-docker/master/web/nginx.conf /etc/nginx/sites-enabled/web.conf
COPY --chmod=755 ./src /run/ COPY ./src /run/
RUN chmod +x /run/*.sh && chmod 755 -R /var/www/
VOLUME /storage VOLUME /storage
EXPOSE 22 5900 8006 EXPOSE 22 5900 8006
ENV BOOT="alpine" ENV CPU_CORES "1"
ENV CPU_CORES="2" ENV RAM_SIZE "1G"
ENV RAM_SIZE="2G" ENV DISK_SIZE "16G"
ENV DISK_SIZE="16G" ENV BOOT "http://example.com/image.iso"
ARG VERSION_ARG "0.0"
RUN echo "$VERSION_ARG" > /run/version
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]

View file

@ -1,17 +0,0 @@
services:
qemu:
container_name: qemu
image: qemux/qemu-arm
environment:
BOOT: "alpine"
devices:
- /dev/kvm
- /dev/net/tun
cap_add:
- NET_ADMIN
ports:
- 8006:8006
volumes:
- ./qemu:/storage
restart: always
stop_grace_period: 2m

20
docker-compose.yml Normal file
View file

@ -0,0 +1,20 @@
version: "3"
services:
qemu:
container_name: qemu
image: qemux/qemu-arm
environment:
RAM_SIZE: "1G"
CPU_CORES: "1"
DISK_SIZE: "16G"
BOOT: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/aarch64/alpine-virt-3.19.1-aarch64.iso"
devices:
- /dev/kvm
device_cgroup_rules:
- 'c *:* rwm'
cap_add:
- NET_ADMIN
ports:
- 8006:8006
stop_grace_period: 2m
restart: on-failure

View file

@ -1,86 +0,0 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: qemu-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 16Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: qemu
labels:
name: qemu
spec:
replicas: 1
selector:
matchLabels:
app: qemu
template:
metadata:
labels:
app: qemu
spec:
containers:
- name: qemu
image: qemux/qemu-arm
env:
- name: BOOT
value: "alpine"
- name: DISK_SIZE
value: "16G"
ports:
- containerPort: 8006
name: http
protocol: TCP
- containerPort: 5900
name: vnc
protocol: TCP
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /storage
name: storage
- mountPath: /dev/kvm
name: dev-kvm
- mountPath: /dev/net/tun
name: dev-tun
terminationGracePeriodSeconds: 120
volumes:
- name: storage
persistentVolumeClaim:
claimName: qemu-pvc
- hostPath:
path: /dev/kvm
name: dev-kvm
- hostPath:
path: /dev/net/tun
type: CharDevice
name: dev-tun
---
apiVersion: v1
kind: Service
metadata:
name: qemu
spec:
internalTrafficPolicy: Cluster
ports:
- name: http
port: 8006
protocol: TCP
targetPort: 8006
- name: vnc
port: 5900
protocol: TCP
targetPort: 5900
selector:
app: qemu
type: ClusterIP

273
readme.md
View file

@ -1,153 +1,81 @@
<h1 align="center">QEMU ARM64<br /> <h1 align="center">Qemu ARM<br />
<div align="center"> <div align="center">
<a href="https://github.com/qemus/qemu-arm"><img src="https://github.com/qemus/qemu-arm/raw/master/.github/logo.png" title="Logo" style="max-width:100%;" width="128" /></a> <img src="https://github.com/qemus/qemu-arm/raw/master/.github/logo.png" title="Logo" style="max-width:100%;" width="128" />
</div> </div>
<div align="center"> <div align="center">
[![Build]][build_url] [![Build]][build_url]
[![Version]][tag_url] [![Version]][tag_url]
[![Size]][tag_url] [![Size]][tag_url]
[![Package]][pkg_url]
[![Pulls]][hub_url] [![Pulls]][hub_url]
</div></h1> </div></h1>
Docker container for running ARM-based virtual machines using QEMU, for devices like the Raspberry Pi 5 and many others. QEMU in a docker container for running ARM-based virtual machines.
## Features ✨ It uses high-performance QEMU options (like KVM acceleration, kernel-mode networking, IO threading, etc.) to achieve near-native speed.
- Web-based viewer to control the machine directly from your browser ## Features
- Supports `.iso`, `.img`, `.qcow2`, `.vhd`, `.vhdx`, `.vdi`, `.vmdk` and `.raw` disk formats - Multi-platform
- KVM acceleration
- Web-based viewer
- High-performance options (like KVM acceleration, kernel-mode networking, IO threading, etc.) to achieve near-native speed ## Usage
## Usage 🐳 Via `docker-compose.yml`
##### Via Docker Compose:
```yaml ```yaml
version: "3"
services: services:
qemu: qemu:
container_name: qemu container_name: qemu
image: qemux/qemu-arm image: qemux/qemu-arm
environment: environment:
BOOT: "alpine" BOOT: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/aarch64/alpine-virt-3.19.1-aarch64.iso"
devices: devices:
- /dev/kvm - /dev/kvm
- /dev/net/tun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 8006:8006 - 8006:8006
volumes:
- ./qemu:/storage
restart: always
stop_grace_period: 2m stop_grace_period: 2m
restart: on-failure
``` ```
##### Via Docker CLI: Via `docker run`
```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 -e "BOOT=http://example.com/image.iso" -p 8006:8006 --device=/dev/kvm --cap-add NET_ADMIN qemux/qemu-arm
``` ```
##### Via Kubernetes: ## FAQ
```shell * ### How do I use it?
kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/master/kubernetes.yml
```
##### Via Github Codespaces:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/qemus/qemu)
## FAQ 💬
### How do I use it?
Very simple! These are the steps: Very simple! These are the steps:
- Set the `BOOT` variable to the [operating system](#how-do-i-select-the-operating-system) you want to install. - Set the `BOOT` environment variable to the URL of an ISO image you want to install.
- Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser. - Start the container and connect to [port 8006](http://localhost: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.
Enjoy your brand new machine, and don't forget to star this repo! Enjoy your brand new machine, and don't forget to star this repo!
### How do I select the operating system? * ### How do I increase the amount of CPU or RAM?
You can use the `BOOT` environment variable in order to specify the operating system that will be downloaded: By default, a single CPU core and 1 GB of RAM are allocated to the container.
To increase this, add the following environment variables:
```yaml ```yaml
environment: environment:
BOOT: "alpine" RAM_SIZE: "4G"
``` CPU_CORES: "4"
Select from the values below:
| **Value** | **Operating System** | **Size** |
|---|---|---|
| `alma` | Alma Linux | 1.7 GB |
| `alpine` | Alpine Linux | 60 MB |
| `cachy` | CachyOS | 2.6 GB |
| `centos` | CentOS | 6.4 GB |
| `debian` | Debian | 3.7 GB |
| `fedora` | Fedora | 2.9 GB |
| `gentoo` | Gentoo | 1.3 GB |
| `kali` | Kali Linux | 3.4 GB |
| `nixos` | NixOS | 2.4 GB |
| `suse` | OpenSUSE | 1.0 GB |
| `oracle` | Oracle Linux | 1.0 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?
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
environment:
BOOT: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/aarch64/alpine-virt-3.19.1-aarch64.iso"
``` ```
The `BOOT` URL accepts files in any of the following formats: * ### How do I change the size of the disk?
| **Extension** | **Format** |
|---|---|
| `.img` | Raw |
| `.raw` | Raw |
| `.iso` | Optical |
| `.qcow2` | QEMU |
| `.vmdk` | VMware |
| `.vhd` | VirtualPC |
| `.vhdx` | Hyper-V |
| `.vdi` | VirtualBox |
It will also accept files such as `.img.gz`, `.qcow2.xz`, `.iso.zip` and many more, because it will automaticly extract compressed files.
Alternatively you can use a local image file directly, by binding it in your compose file:
```yaml
volumes:
- ./example.iso:/boot.iso
```
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?
To change the storage location, include the following bind mount in your compose file:
```yaml
volumes:
- ./qemu:/storage
```
Replace the example path `./qemu` with the desired storage folder or named volume.
### How do I change the size of the disk?
To expand the default size of 16 GB, add the `DISK_SIZE` setting to your compose file and set it to your preferred capacity: To expand the default size of 16 GB, add the `DISK_SIZE` setting to your compose file and set it to your preferred capacity:
@ -156,85 +84,28 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
DISK_SIZE: "128G" DISK_SIZE: "128G"
``` ```
> [!TIP] This can also be used to resize the existing disk to a larger capacity without any data loss.
> This can also be used to resize the existing disk to a larger capacity without any data loss.
### How do I change the amount of CPU or RAM? * ### How do I change the storage location?
By default, the container will be allowed to use a maximum of 2 CPU cores and 2 GB of RAM. To change the storage location, include the following bind mount in your compose file:
If you want to adjust this, you can specify the desired amount using the following environment variables:
```yaml ```yaml
environment: volumes:
RAM_SIZE: "8G" - /var/qemu:/storage
CPU_CORES: "4"
``` ```
### How do I increase the display resolution? Replace the example path `/var/qemu` with the desired storage folder.
For maximum compatibility, the display output will be a simple framebuffer by default. While this isn't the most optimal, it doesn't require any drivers. * ### How do I boot a x86 image?
If your guest OS bundles the `virtio-gpu` driver (as most Linux distributions do), you can add the following to your compose file: You can use [qemu-docker](https://github.com/qemus/qemu-docker/) to run x86 and x64 images on ARM.
```yaml * ### How do I boot a local image?
environment:
VGA: "virtio-gpu"
```
to add a virtual graphics cards to your machine that allows for higher resolutions. To skip the download, rename your image to `boot.iso` and place it in an empty `/storage` folder.
> [!NOTE] * ### How do I assign an individual IP address to the container?
> Using this method your screen will stay black during the boot process, until the point where the driver is actually loaded.
### How do I boot Windows?
Use [dockur/windows-arm](https://github.com/dockur/windows-arm) instead, as it includes all the drivers required during installation, amongst many other features.
### How do I boot x86/x64 images?
You can use the [qemu](https://github.com/qemus/qemu/) container to run x86 and x64 images on ARM.
### How do I verify if my system supports KVM?
First check if your software is compatible using this chart:
| **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
sudo apt install cpu-checker
sudo kvm-ok
```
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
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.
### How do I expose network ports?
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?
By default, the container uses bridge networking, which shares the IP address with the host. By default, the container uses bridge networking, which shares the IP address with the host.
@ -268,50 +139,39 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default. An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default.
> [!IMPORTANT] Please note that this IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
> This IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround.
### How can the VM acquire an IP address from my router? * ### How can the VM acquire an IP address from my router?
After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for the VM to become part of your home network by requesting an IP from your router, just like a real PC. After configuring the container for macvlan (see above), it is possible for the VM to become part of your home network by requesting an IP from your router, just like a real PC.
To enable this mode, in which the container and the VM will have separate IP addresses, add the following lines to your compose file: To enable this mode, add the following lines to your compose file:
```yaml ```yaml
environment: environment:
DHCP: "Y" DHCP: "Y"
devices:
- /dev/vhost-net
device_cgroup_rules: device_cgroup_rules:
- 'c *:* rwm' - 'c *:* rwm'
``` ```
### How do I add multiple disks? Please note that in this mode, the container and the VM will each have their own separate IPs. The container will keep the macvlan IP, and the VM will use the DHCP IP.
To create additional disks, modify your compose file like this: * ### 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:
```yaml ```yaml
environment: environment:
DISK2_SIZE: "32G" DEVICE: "/dev/sda"
DISK3_SIZE: "64G" DEVICE2: "/dev/sdb"
volumes:
- ./example2:/storage2
- ./example3:/storage3
```
### How do I pass-through a disk?
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way:
```yaml
devices: devices:
- /dev/sdb:/disk1 - /dev/sda
- /dev/sdc1:/disk2 - /dev/sdb
``` ```
Use `/disk1` if you want it to become your main drive, and use `/disk2` and higher to add them as secondary drives. Use `DEVICE` if you want it to become your main drive, and use `DEVICE2` and higher to add them as secondary drives.
### How do I pass-through a USB device? * ### How do I pass-through a USB device?
To pass-through a USB device, first lookup its vendor and product id via the `lsusb` command, then add them to your compose file like this: To pass-through a USB device, first lookup its vendor and product id via the `lsusb` command, then add them to your compose file like this:
@ -322,24 +182,18 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
- /dev/bus/usb - /dev/bus/usb
``` ```
### How do I share files with the host? * ### How do I verify if my system supports KVM?
To share files with the host, first ensure that your guest OS has `9pfs` support compiled in or available as a kernel module. If so, add the following volume to your compose file: To verify if your system supports KVM, run the following commands:
```yaml ```bash
volumes: sudo apt install cpu-checker
- ./example:/shared sudo kvm-ok
``` ```
Then start the container and execute the following command in the guest: If you receive an error from `kvm-ok` indicating that KVM acceleration can't be used, check the virtualization settings in the BIOS.
```shell * ### How do I provide custom arguments to QEMU?
mount -t 9p -o trans=virtio shared /mnt/example
```
Now the `./example` directory on the host will be available as `/mnt/example` in the guest.
### How can I provide custom arguments to QEMU?
You can create the `ARGUMENTS` environment variable to provide additional arguments to QEMU at runtime: You can create the `ARGUMENTS` environment variable to provide additional arguments to QEMU at runtime:
@ -348,23 +202,14 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu-arm/refs/heads/mas
ARGUMENTS: "-device usb-tablet" ARGUMENTS: "-device usb-tablet"
``` ```
If you want to see the full command-line arguments used, you can set: ## Stars
```yaml
environment:
DEBUG: "Y"
```
## Stars 🌟
[![Stars](https://starchart.cc/qemus/qemu-arm.svg?variant=adaptive)](https://starchart.cc/qemus/qemu-arm) [![Stars](https://starchart.cc/qemus/qemu-arm.svg?variant=adaptive)](https://starchart.cc/qemus/qemu-arm)
[build_url]: https://github.com/qemus/qemu-arm/ [build_url]: https://github.com/qemus/qemu-arm/
[hub_url]: https://hub.docker.com/r/qemux/qemu-arm/ [hub_url]: https://hub.docker.com/r/qemux/qemu-arm/
[tag_url]: https://hub.docker.com/r/qemux/qemu-arm/tags [tag_url]: https://hub.docker.com/r/qemux/qemu-arm/tags
[pkg_url]: https://github.com/qemus/qemu-arm/pkgs/container/qemu-arm
[Build]: https://github.com/qemus/qemu-arm/actions/workflows/build.yml/badge.svg [Build]: https://github.com/qemus/qemu-arm/actions/workflows/build.yml/badge.svg
[Size]: https://img.shields.io/docker/image-size/qemux/qemu-arm/latest?color=066da5&label=size [Size]: https://img.shields.io/docker/image-size/qemux/qemu-arm/latest?color=066da5&label=size
[Pulls]: https://img.shields.io/docker/pulls/qemux/qemu-arm.svg?style=flat&label=pulls&logo=docker [Pulls]: https://img.shields.io/docker/pulls/qemux/qemu-arm.svg?style=flat&label=pulls&logo=docker
[Version]: https://img.shields.io/docker/v/qemux/qemu-arm/latest?arch=arm64&sort=semver&color=066da5 [Version]: https://img.shields.io/docker/v/qemux/qemu-arm/latest?arch=arm64&sort=semver&color=066da5
[Package]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2Fqemus%2Fqemu-arm%2Fqemu-arm.json&query=%24.downloads&logo=github&style=flat&color=066da5&label=pulls

View file

@ -2,115 +2,55 @@
set -Eeuo pipefail set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${BIOS:=""}" # BIOS file : "${BIOS:=""}" # Bios file
: "${SECURE:="off"}" # Secure boot
BOOT_DESC=""
BOOT_OPTS="" BOOT_OPTS=""
[ -n "$BIOS" ] && BOOT_MODE="custom" DIR="/usr/share/qemu"
case "${BOOT_MODE,,}" in case "${BOOT_MODE,,}" in
"uefi" | "" ) uefi)
BOOT_MODE="uefi" ROM="AAVMF_CODE.fd"
ROM="AAVMF_CODE.no-secboot.fd"
VARS="AAVMF_VARS.fd" VARS="AAVMF_VARS.fd"
;; ;;
"secure" ) secure)
SECURE="on" ROM="AAVMF_CODE.fd"
BOOT_DESC=" securely"
ROM="AAVMF_CODE.secboot.fd"
VARS="AAVMF_VARS.fd" VARS="AAVMF_VARS.fd"
;; ;;
"windows" ) windows)
ROM="AAVMF_CODE.no-secboot.fd"
VARS="AAVMF_VARS.fd"
BOOT_OPTS="-rtc base=localtime"
;;
"windows_secure" )
SECURE="on"
BOOT_DESC=" securely"
ROM="AAVMF_CODE.ms.fd" ROM="AAVMF_CODE.ms.fd"
VARS="AAVMF_VARS.ms.fd" VARS="AAVMF_VARS.ms.fd"
BOOT_OPTS="-rtc base=localtime"
;;
"legacy" )
BOOT_DESC=" with SeaBIOS"
;;
"custom" )
BOOT_OPTS="-bios $BIOS"
BOOT_DESC=" with custom BIOS file"
;; ;;
*) *)
error "Unknown BOOT_MODE, value \"${BOOT_MODE}\" is not recognized!" info "Unknown boot mode '${BOOT_MODE}', defaulting to 'uefi'"
exit 33 BOOT_MODE="uefi"
ROM="AAVMF_CODE.fd"
VARS="AAVMF_VARS.fd"
;; ;;
esac esac
case "${BOOT_MODE,,}" in if [ -n "$BIOS" ]; then
"uefi" | "secure" | "windows" | "windows_secure" )
BOOT_OPTS="$BOOT_OPTS -bios $DIR/$BIOS"
return 0
fi
AAVMF="/usr/share/AAVMF/" AAVMF="/usr/share/AAVMF/"
DEST="$STORAGE/${BOOT_MODE,,}" DEST="$STORAGE/${BOOT_MODE,,}"
if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then if [ ! -f "$DEST.rom" ]; then
[ ! -s "$AAVMF/$ROM" ] || [ ! -f "$AAVMF/$ROM" ] && error "UEFI boot file ($AAVMF/$ROM) not found!" && exit 44 [ ! -f "$AAVMF/$ROM" ] && error "UEFI boot file ($AAVMF/$ROM) not found!" && exit 44
rm -f "$DEST.rom"
dd if=/dev/zero "of=$DEST.rom" bs=1M count=64 status=none dd if=/dev/zero "of=$DEST.rom" bs=1M count=64 status=none
dd "if=$AAVMF/$ROM" "of=$DEST.rom" conv=notrunc status=none dd "if=$AAVMF/$ROM" "of=$DEST.rom" conv=notrunc status=none
fi fi
if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then if [ ! -f "$DEST.vars" ]; then
[ ! -s "$AAVMF/$VARS" ] || [ ! -f "$AAVMF/$VARS" ] && error "UEFI vars file ($AAVMF/$VARS) not found!" && exit 45 [ ! -f "$AAVMF/$VARS" ] && error "UEFI vars file ($AAVMF/$VARS) not found!" && exit 45
rm -f "$DEST.vars"
dd if=/dev/zero "of=$DEST.vars" bs=1M count=64 status=none dd if=/dev/zero "of=$DEST.vars" bs=1M count=64 status=none
dd "if=$AAVMF/$VARS" "of=$DEST.vars" conv=notrunc status=none dd "if=$AAVMF/$VARS" "of=$DEST.vars" conv=notrunc status=none
fi fi
BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on" BOOT_OPTS="$BOOT_OPTS -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on"
BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw" BOOT_OPTS="$BOOT_OPTS -drive file=$DEST.vars,if=pflash,unit=1,format=raw"
;;
esac
MSRS="/sys/module/kvm/parameters/ignore_msrs"
if [ -e "$MSRS" ]; then
result=$(<"$MSRS")
result="${result//[![:print:]]/}"
if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then
echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
fi
fi
CLOCKSOURCE="tsc"
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
if [ ! -f "$CLOCK" ]; then
warn "file \"$CLOCK\" cannot not found?"
else
result=$(<"$CLOCK")
result="${result//[![:print:]]/}"
case "${result,,}" in
"${CLOCKSOURCE,,}" ) ;;
"kvm-clock" ) info "Nested KVM 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'." ;;
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
esac
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

View file

@ -1,37 +1,22 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${UUID:=""}"
: "${SERIAL:="mon:stdio"}" : "${SERIAL:="mon:stdio"}"
: "${USB:="qemu-xhci,id=xhci,p2=7,p3=7"}" : "${USB:="qemu-xhci,id=xhci"}"
: "${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"}"
DEF_OPTS="-nodefaults" DEF_OPTS="-nodefaults"
SERIAL_OPTS="-serial $SERIAL" SERIAL_OPTS="-serial $SERIAL"
CPU_OPTS="-cpu $CPU_FLAGS -smp $SMP" USB_OPTS="-device $USB -device usb-kbd -device usb-tablet"
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" CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
MAC_OPTS="-machine type=${MACHINE},secure=${SECURE},dump-guest-core=off${KVM_OPTS}" MON_OPTS="-monitor $MONITOR -name ${APP// /-},process=${APP,,// /-},debug-threads=on"
MAC_OPTS="-machine type=${MACHINE},secure=off,dump-guest-core=off${KVM_OPTS}"
DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4"
DEV_OPTS="$DEV_OPTS -object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS="$DEV_OPTS -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0,addr=0x1c"
[ -n "$UUID" ] && MAC_OPTS+=" -uuid $UUID" 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"
[ -n "$SM_BIOS" ] && MAC_OPTS+=" $SM_BIOS"
DEV_OPTS="-object rng-random,id=objrng0,filename=/dev/urandom"
DEV_OPTS+=" -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pcie.0"
if [[ "${BOOT_MODE,,}" != "windows"* ]]; then
DEV_OPTS+=" -device virtio-balloon-pci,id=balloon0,bus=pcie.0"
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=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ') ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
if [[ "${DISPLAY,,}" == "web" ]]; then if [[ "${DISPLAY,,}" == "web" ]]; then
@ -47,32 +32,4 @@ else
fi fi
fi fi
# Check available memory as the very last step
if [[ "$RAM_CHECK" != [Nn]* ]]; then
RAM_AVAIL=$(free -b | grep -m 1 Mem: | awk '{print $7}')
AVAIL_MEM=$(formatBytes "$RAM_AVAIL")
if (( (RAM_WANTED + RAM_SPARE) > RAM_AVAIL )); then
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"
else
if (( (RAM_WANTED + (RAM_SPARE * 3)) > RAM_AVAIL )); then
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $AVAIL_MEM of memory available, please consider a lower value."
if [[ "${FS,,}" != "zfs" ]]; then
warn "$msg"
else
info "$msg"
fi
fi
fi
fi
if [[ "$DEBUG" == [Yy1]* ]]; then
printf "Arguments:\n\n%s\n\n" "${ARGS// -/$'\n-'}"
fi
return 0 return 0

503
src/disk.sh Normal file
View file

@ -0,0 +1,503 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Docker environment variables
: "${DISK_IO:="native"}" # I/O Mode, can be set to 'native', 'threads' or 'io_turing'
: "${DISK_FMT:=""}" # Disk file format, can be set to "raw" (default) or "qcow2"
: "${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
BOOT="$STORAGE/$BASE"
[ ! -f "$BOOT" ] && BOOT="/dev/null"
DISK_OPTS="-object iothread,id=io2"
DISK_OPTS="$DISK_OPTS -drive id=cdrom0,media=cdrom,if=none,format=raw,readonly=on,file=$BOOT"
DISK_OPTS="$DISK_OPTS -device virtio-scsi-pci,id=scsi0,iothread=io2,addr=0x5"
DISK_OPTS="$DISK_OPTS -device scsi-cd,bus=scsi0.0,drive=cdrom0,bootindex=$BOOT_INDEX"
DRIVERS="$STORAGE/drivers.iso"
[ ! -f "$DRIVERS" ] && DRIVERS="/run/drivers.iso"
if [ -f "$DRIVERS" ]; then
DISK_OPTS="$DISK_OPTS -drive id=cdrom1,media=cdrom,if=none,format=raw,readonly=on,file=$DRIVERS -device usb-storage,drive=cdrom1"
fi
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,,}" == "xfs" || "${FS,,}" == "btrfs" || "${FS,,}" == "bcachefs" ]]; then
return 0
fi
return 1
}
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 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
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
error "Not enough free space to create a $DISK_DESC of $DISK_SPACE in $DIR, it has only $SPACE_GB 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 $DISK_TYPE $DISK_DESC image in $DISK_FMT format..."
local FAIL="Could not create a $DISK_TYPE $DISK_FMT $DISK_DESC image of $DISK_SPACE ($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="$DISK_PARAM,nocow=on"
[ -n "$DISK_FLAGS" ] && DISK_PARAM="$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
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
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
error "Not enough free space to resize $DISK_DESC to $DISK_SPACE in $DIR, it has only $SPACE_GB GB available.."
error "Please specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation by setting ALLOCATE=N." && exit 74
fi
fi
local GB=$(( (CUR_SIZE + 1073741823)/1073741824 ))
MSG="Resizing $DISK_DESC from ${GB}G to $DISK_SPACE..."
info "$MSG" && html "$MSG"
local FAIL="Could not resize the $DISK_TYPE $DISK_FMT $DISK_DESC image from ${GB}G to $DISK_SPACE ($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
# 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
local SPACE_GB=$(( (SPACE + 1073741823)/1073741824 ))
error "Not enough free space to convert $DISK_DESC to $DST_FMT in $DIR, it has only $SPACE_GB GB available..."
error "Please free up some disk space or disable preallocation by setting ALLOCATE=N." && exit 76
fi
fi
html "Converting $DISK_DESC to $DST_FMT..."
info "Converting $DISK_DESC to $DST_FMT, please wait until completed..."
local CONV_FLAGS="-p"
local DISK_PARAM="$DISK_ALLOC"
isCow "$FS" && DISK_PARAM="$DISK_PARAM,nocow=on"
if [[ "$DST_FMT" != "raw" ]]; then
if [[ "$ALLOCATE" == [Nn]* ]]; then
CONV_FLAGS="$CONV_FLAGS -c"
fi
[ -n "$DISK_FLAGS" ] && DISK_PARAM="$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_TYPE $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
html "Conversion of $DISK_DESC completed..."
info "Conversion of $DISK_DESC 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 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_ID=$1
local DISK_FILE=$2
local DISK_INDEX=$3
local DISK_ADDRESS=$4
local DISK_FMT=$5
local result="-drive file=$DISK_FILE,if=none,id=drive-$DISK_ID,format=$DISK_FMT,cache=$DISK_CACHE,aio=$DISK_IO,discard=$DISK_DISCARD,detect-zeroes=on"
result="$result \
-device virtio-scsi-pci,id=hw-$DISK_ID,iothread=io2,bus=pcie.0,addr=$DISK_ADDRESS \
-device scsi-hd,bus=hw-$DISK_ID.0,channel=0,scsi-id=0,lun=0,drive=drive-$DISK_ID,id=$DISK_ID,rotation_rate=$DISK_ROTATION,bootindex=$DISK_INDEX"
echo "$result"
return 0
}
addDisk () {
local DISK_ID=$1
local DISK_BASE=$2
local DISK_EXT=$3
local DISK_DESC=$4
local DISK_SPACE=$5
local DISK_INDEX=$6
local DISK_ADDRESS=$7
local DISK_FMT=$8
local DISK_FILE="$DISK_BASE.$DISK_EXT"
local DIR DATA_SIZE FS PREV_FMT PREV_EXT CUR_SIZE OPTS
DIR=$(dirname "$DISK_FILE")
[ ! -d "$DIR" ] && return 0
[ -z "$DISK_SPACE" ] && DISK_SPACE="16G"
DISK_SPACE=$(echo "${DISK_SPACE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
DATA_SIZE=$(numfmt --from=iec "$DISK_SPACE")
if (( DATA_SIZE < 1 )); then
error "Invalid value for ${DISK_DESC^^}_SIZE: $DISK_SPACE" && exit 73
fi
FS=$(stat -f -c %T "$DIR")
checkFS "$FS" "$DISK_FILE" "$DISK_DESC" || exit $?
if ! [ -f "$DISK_FILE" ] ; then
if [[ "${DISK_FMT,,}" != "raw" ]]; then
PREV_FMT="raw"
else
PREV_FMT="qcow2"
fi
PREV_EXT=$(fmt2ext "$PREV_FMT")
if [ -f "$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 [ -f "$DISK_FILE" ]; then
CUR_SIZE=$(getSize "$DISK_FILE")
if (( DATA_SIZE > CUR_SIZE )); then
resizeDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
fi
else
createDisk "$DISK_FILE" "$DISK_SPACE" "$DISK_DESC" "$DISK_FMT" "$FS" || exit $?
fi
OPTS=$(createDevice "$DISK_ID" "$DISK_FILE" "$DISK_INDEX" "$DISK_ADDRESS" "$DISK_FMT")
DISK_OPTS="$DISK_OPTS $OPTS"
return 0
}
addDevice () {
local DISK_ID=$1
local DISK_DEV=$2
local DISK_DESC=$3
local DISK_INDEX=$4
local DISK_ADDRESS=$5
[ -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
local OPTS
OPTS=$(createDevice "$DISK_ID" "$DISK_DEV" "$DISK_INDEX" "$DISK_ADDRESS" "raw")
DISK_OPTS="$DISK_OPTS $OPTS"
return 0
}
html "Initializing disks..."
DISK1_FILE="$STORAGE/data"
DISK2_FILE="/storage2/data2"
DISK3_FILE="/storage3/data3"
DISK4_FILE="/storage4/data4"
if [ -z "$DISK_FMT" ]; then
if [ -f "$DISK1_FILE.qcow2" ]; then
DISK_FMT="qcow2"
else
DISK_FMT="raw"
fi
fi
DISK_EXT=$(fmt2ext "$DISK_FMT")
if [ -z "$ALLOCATE" ]; then
ALLOCATE="N"
fi
if [[ "$ALLOCATE" == [Nn]* ]]; then
DISK_TYPE="growable"
DISK_ALLOC="preallocation=off"
else
DISK_TYPE="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:=""}"
if [ -n "$DEVICE" ]; then
addDevice "userdata" "$DEVICE" "device" "3" "0xa" || exit $?
else
addDisk "userdata" "$DISK1_FILE" "$DISK_EXT" "disk" "$DISK_SIZE" "3" "0xa" "$DISK_FMT" || exit $?
fi
if [ -n "$DEVICE2" ]; then
addDevice "userdata2" "$DEVICE2" "device2" "4" "0xb" || exit $?
else
addDisk "userdata2" "$DISK2_FILE" "$DISK_EXT" "disk2" "$DISK2_SIZE" "4" "0xb" "$DISK_FMT" || exit $?
fi
if [ -n "$DEVICE3" ]; then
addDevice "userdata3" "$DEVICE3" "device3" "5" "0xc" || exit $?
else
addDisk "userdata3" "$DISK3_FILE" "$DISK_EXT" "disk3" "$DISK3_SIZE" "5" "0xc" "$DISK_FMT" || exit $?
fi
if [ -n "$DEVICE4" ]; then
addDevice "userdata4" "$DEVICE4" "device4" "6" "0xd" || exit $?
else
addDisk "userdata4" "$DISK4_FILE" "$DISK_EXT" "disk4" "$DISK4_SIZE" "6" "0xd" "$DISK_FMT" || exit $?
fi
html "Initialized disks successfully..."
return 0

View file

@ -3,25 +3,26 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${VGA:="ramfb"}" # VGA adaptor : "${VGA:=""}" # VGA adaptor
: "${DISPLAY:="web"}" # Display type : "${DISPLAY:="web"}" # Display type
[[ "$DISPLAY" == ":0" ]] && DISPLAY="web" if [[ "${BOOT_MODE,,}" != "windows" ]]; then
[ -z "$VGA" ] && VGA="virtio-gpu"
else
[ -z "$VGA" ] && VGA="ramfb"
fi
case "${DISPLAY,,}" in case "${DISPLAY,,}" in
"vnc" ) vnc)
DISPLAY_OPTS="-display vnc=:0 -device $VGA" DISPLAY_OPTS="-display vnc=:0 -device $VGA"
;; ;;
"web" ) web)
DISPLAY_OPTS="-display vnc=:0,websocket=5700 -device $VGA" DISPLAY_OPTS="-display vnc=:0,websocket=5700 -device $VGA"
;; ;;
"ramfb" ) ramfb)
DISPLAY_OPTS="-display vnc=:0,websocket=5700 -device ramfb" DISPLAY_OPTS="-display vnc=:0,websocket=5700 -device ramfb"
;; ;;
"disabled" ) none)
DISPLAY_OPTS="-display none -device $VGA"
;;
"none" )
DISPLAY_OPTS="-display none" DISPLAY_OPTS="-display none"
;; ;;
*) *)

View file

@ -1,17 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${APP:="QEMU"}" APP="QEMU"
: "${MACHINE:="virt"}" SUPPORT="https://github.com/qemus/qemu-arm"
: "${PLATFORM:="arm64"}"
: "${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 . install.sh # Get bootdisk
. install.sh # Download image
. disk.sh # Initialize disks . disk.sh # Initialize disks
. display.sh # Initialize graphics . display.sh # Initialize graphics
. network.sh # Initialize network . network.sh # Initialize network
@ -21,11 +17,7 @@ cd /run
trap - ERR trap - ERR
version=$(qemu-system-aarch64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') info "Booting image using $VERS..."
info "Booting image${BOOT_DESC} using QEMU v$version..."
if [ -z "$CPU_PIN" ]; then [[ "$DEBUG" == [Yy1]* ]] && set -x
exec qemu-system-aarch64 ${ARGS:+ $ARGS} exec qemu-system-aarch64 ${ARGS:+ $ARGS}
else
exec taskset -c "$CPU_PIN" qemu-system-aarch64 ${ARGS:+ $ARGS}
fi

58
src/install.sh Normal file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# 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
BASE="boot.img"
[ ! -f "$STORAGE/$BASE" ] && BASE="boot.iso"
[ ! -f "$STORAGE/$BASE" ] && BASE="Boot.img"
[ ! -f "$STORAGE/$BASE" ] && BASE="Boot.iso"
[ ! -f "$STORAGE/$BASE" ] && BASE="boot.IMG"
[ ! -f "$STORAGE/$BASE" ] && BASE="boot.ISO"
[ ! -f "$STORAGE/$BASE" ] && BASE="BOOT.IMG"
[ ! -f "$STORAGE/$BASE" ] && BASE="BOOT.ISO"
[ -f "$STORAGE/$BASE" ] && return 0
if [ -z "$BOOT" ]; then
error "No boot disk specified, set BOOT= to the URL of an ISO file." && exit 64
fi
BASE=$(basename "$BOOT")
[ -f "$STORAGE/$BASE" ] && return 0
BASE=$(basename "${BOOT%%\?*}")
: "${BASE//+/ }"; printf -v BASE '%b' "${_//%/\\x}"
BASE=$(echo "$BASE" | sed -e 's/[^A-Za-z0-9._-]/_/g')
[ -f "$STORAGE/$BASE" ] && return 0
TMP="$STORAGE/${BASE%.*}.tmp"
rm -f "$TMP"
MSG="Downloading $BASE..."
info "$MSG" && html "$MSG"
/run/progress.sh "$TMP" "Downloading $BASE ([P])..." &
{ wget "$BOOT" -O "$TMP" -q --no-check-certificate --show-progress "$PROGRESS"; rc=$?; } || :
fKill "progress.sh"
(( rc != 0 )) && error "Failed to download $BOOT , reason: $rc" && exit 60
[ ! -f "$TMP" ] && error "Failed to download $BOOT" && exit 61
html "Download finished successfully..."
SIZE=$(stat -c%s "$TMP")
if ((SIZE<100000)); then
error "Invalid ISO file: Size is smaller than 100 KB" && exit 62
fi
mv -f "$TMP" "$STORAGE/$BASE"
return 0

297
src/network.sh Normal file
View file

@ -0,0 +1,297 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# Docker environment variables
: "${MAC:=""}"
: "${DHCP:="N"}"
: "${HOST_PORTS:=""}"
: "${VM_NET_DEV:=""}"
: "${VM_NET_TAP:="qemu"}"
: "${VM_NET_MAC:="$MAC"}"
: "${VM_NET_HOST:="QEMU"}"
: "${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 a macvtap network for the VM guest
{ ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge ; rc=$?; } || :
if (( rc != 0 )); then
error "Cannot create macvtap interface. Please make sure the network type is 'macvlan' and not 'ipvlan',"
error "and that the NET_ADMIN capability has been added to the container: --cap-add NET_ADMIN" && exit 16
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" && exit 18
[[ ! -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)" && exit 20
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'" && exit 21
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" && exit 22
fi
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
return 0
}
configureDNS() {
# dnsmasq configuration:
DNSMASQ_OPTS="$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="$DNSMASQ_OPTS --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1"
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
[[ "$DEBUG" == [Yy1]* ]] && set -x
if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
error "Failed to start dnsmasq, reason: $?" && exit 29
fi
{ set +x; } 2>/dev/null
[[ "$DEBUG" == [Yy1]* ]] && echo
return 0
}
getPorts() {
local list=$1
local vnc="5900"
local web="8006"
[ -z "$list" ] && list="$web" || list="$list,$web"
if [[ "${DISPLAY,,}" == "vnc" ]] || [[ "${DISPLAY,,}" == "web" ]]; then
[ -z "$list" ] && list="$vnc" || list="$list,$vnc"
fi
[ -z "$list" ] && return 0
if [[ "$list" != *","* ]]; then
echo " ! --dport $list"
else
echo " -m multiport ! --dports $list"
fi
return 0
}
configureNAT() {
# 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 "TUN device missing. $ADD_ERR --cap-add NET_ADMIN" && exit 25
fi
# Check port forwarding flag
if [[ $(< /proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
{ sysctl -w net.ipv4.ip_forward=1 ; rc=$?; } || :
if (( rc != 0 )); then
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && exit 24
fi
fi
# Create a bridge with a static IP for the VM guest
VM_NET_IP='20.20.20.21'
{ ip link add dev dockerbridge type bridge ; rc=$?; } || :
if (( rc != 0 )); then
error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && exit 23
fi
ip address add ${VM_NET_IP%.*}.1/24 broadcast ${VM_NET_IP%.*}.255 dev dockerbridge
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
ip tuntap add dev "$VM_NET_TAP" mode tap
while ! ip link set "$VM_NET_TAP" up promisc on; do
info "Waiting for TAP to become available..."
sleep 2
done
ip link set dev "$VM_NET_TAP" master dockerbridge
# 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=$(getPorts "$HOST_PORTS")
iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE
# shellcheck disable=SC2086
iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP"
iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"
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 || true
fi
NET_OPTS="-netdev tap,ifname=$VM_NET_TAP,script=no,downscript=no,id=hostnet0"
{ exec 40>>/dev/vhost-net; rc=$?; } 2>/dev/null || :
(( rc == 0 )) && NET_OPTS="$NET_OPTS,vhost=on,vhostfd=40"
configureDNS
return 0
}
closeNetwork() {
# Shutdown nginx
nginx -s stop 2> /dev/null
fWait "nginx"
exec 30<&- || true
exec 40<&- || true
if [[ "$DHCP" == [Yy1]* ]]; then
ip link set "$VM_NET_TAP" down || true
ip link delete "$VM_NET_TAP" || true
else
local pid="/var/run/dnsmasq.pid"
[ -f "$pid" ] && pKill "$(<"$pid")"
ip link set "$VM_NET_TAP" down promisc off || true
ip link delete "$VM_NET_TAP" || true
ip link set dockerbridge down || true
ip link delete dockerbridge || true
fi
return 0
}
getInfo() {
if [ -z "$VM_NET_DEV" ]; then
# Automaticly detect the default network interface
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 "$MAC" ]; then
# Generate MAC address based on Docker container ID in hostname
MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
fi
VM_NET_MAC="${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 r | grep default | awk '{print $3}')
IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
return 0
}
# ######################################
# Configure Network
# ######################################
if [ ! -c /dev/vhost-net ]; then
if mknod /dev/vhost-net c 10 238; then
chmod 660 /dev/vhost-net
fi
fi
getInfo
html "Initializing network..."
if [[ "$DEBUG" == [Yy1]* ]]; then
info "Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC"
[ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf
echo
fi
if [[ "$DHCP" == [Yy1]* ]]; then
if [[ "$GATEWAY" == "172."* ]] && [[ "$DEBUG" != [Yy1]* ]]; then
error "You can only enable DHCP while the container is on a macvlan network!" && exit 26
fi
# Configuration for DHCP IP
configureDHCP
else
# Configuration for static IP
configureNAT
fi
NET_OPTS="$NET_OPTS -device virtio-net-pci,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0"
html "Initialized network successfully..."
return 0

View file

@ -4,61 +4,30 @@ set -Eeuo pipefail
# Docker environment variables # Docker environment variables
: "${KVM:="Y"}" : "${KVM:="Y"}"
: "${CPU_PIN:=""}"
: "${CPU_FLAGS:=""}" : "${CPU_FLAGS:=""}"
: "${CPU_MODEL:=""}" : "${CPU_MODEL:=""}"
: "${DEF_MODEL:="neoverse-n1"}" : "${DEF_MODEL:="neoverse-n1"}"
if [[ "$CPU" == "Cortex A53" ]] && [[ "$CORES" == "6" ]]; then [[ "$ARCH" != "arm"* ]] && KVM="N"
# Pin to performance cores on Rockchip Orange Pi 4
[ -z "$CPU_PIN" ] && CPU_PIN="4,5"
fi
if [[ "$CPU" == "Cortex A55" ]] && [[ "$CORES" == "8" ]]; then
# Pin to performance cores on Rockchip Orange Pi 5
[ -z "$CPU_PIN" ] && CPU_PIN="4,5,6,7"
fi
if [[ "$CPU" == "Rockchip RK3588"* ]] && [[ "$CORES" == "8" ]]; then
# Pin to performance cores on Rockchip Orange Pi 5 Plus
[ -z "$CPU_PIN" ] && CPU_PIN="4,5,6,7"
fi
if [[ "${ARCH,,}" != "arm64" ]]; then
KVM="N"
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ARM64 instructions, this will cause a major loss of performance."
fi
if [[ "$KVM" != [Nn]* ]]; then if [[ "$KVM" != [Nn]* ]]; then
KVM_ERR="" KVM_ERR=""
if [ ! -e /dev/kvm ]; then if [ ! -e /dev/kvm ]; then
KVM_ERR="(/dev/kvm is missing)" KVM_ERR="(device file 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="(/dev/kvm is unwriteable)" KVM_ERR="(no write access)"
fi fi
fi fi
if [ -n "$KVM_ERR" ]; then if [ -n "$KVM_ERR" ]; then
KVM="N" KVM="N"
if [[ "$OSTYPE" =~ ^darwin ]]; then error "KVM acceleration not available $KVM_ERR, 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." error "See the FAQ on how to enable it, or continue without KVM by setting KVM=N (not recommended)."
else
kernel=$(uname -a)
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 fi
@ -77,7 +46,7 @@ else
KVM_OPTS=" -accel tcg,thread=multi" KVM_OPTS=" -accel tcg,thread=multi"
if [ -z "$CPU_MODEL" ]; then if [ -z "$CPU_MODEL" ]; then
if [[ "${ARCH,,}" == "arm64" ]]; then if [[ "$ARCH" == "arm"* ]]; then
CPU_MODEL="max,pauth-impdef=on" CPU_MODEL="max,pauth-impdef=on"
else else
CPU_MODEL="$DEF_MODEL" CPU_MODEL="$DEF_MODEL"
@ -85,7 +54,7 @@ else
fi fi
if [[ "${BOOT_MODE,,}" == "windows" ]]; then if [[ "${BOOT_MODE,,}" == "windows" ]]; then
MACHINE+=",virtualization=on" MACHINE="$MACHINE,virtualization=on"
fi fi
fi fi

32
src/progress.sh Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -Eeuo pipefail
escape () {
local s
s=${1//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
printf -- %s "$s"
return 0
}
file="$1"
body=$(escape "$2")
info="/run/shm/msg.html"
if [[ "$body" == *"..." ]]; then
body="<p class=\"loading\">${body/.../}</p>"
fi
while true
do
if [ -f "$file" ]; then
bytes=$(du -sb "$file" | cut -f1)
if (( bytes > 1000 )); then
size=$(echo "$bytes" | numfmt --to=iec --suffix=B | sed -r 's/([A-Z])/ \1/')
echo "${body//(\[P\])/($size)}"> "$info"
fi
fi
sleep 1 & wait $!
done

166
src/reset.sh Normal file
View file

@ -0,0 +1,166 @@
#!/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"
echo
# 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
: "${DISK_SIZE:="16G"}" # Initial data disk size
: "${BOOT_INDEX:="10"}" # Boot index of CD drive
: "${BOOT_MODE:="uefi"}" # Boot in UEFI mode
# Helper variables
STORAGE="/storage"
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>"
HOST=$(hostname -s)
KERNEL=$(uname -r | cut -b 1)
MINOR=$(uname -r | cut -d '.' -f2)
ARCH=$(dpkg --print-architecture)
VERS=$(qemu-system-aarch64 --version | head -n 1 | cut -d '(' -f 1)
# 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 [ ! -d "$STORAGE" ]; then
error "Storage folder ($STORAGE) not found!" && exit 13
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//&/\&amp;}
s=${s//</\&lt;}
s=${s//>/\&gt;}
s=${s//'"'/\&quot;}
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
}
# Start webserver
cp -r /var/www/* /run/shm
html "Starting $APP for Docker..."
nginx -e stderr
return 0