mirror of
https://code.dumpstack.io/tools/appvm.git
synced 2024-11-23 04:33:02 +00:00
Rewrite in go, use libvirt
This commit is contained in:
parent
fbf4fa0e5a
commit
eef6ed2ec0
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,9 +0,0 @@
|
|||||||
bin/*
|
|
||||||
!bin/.keep
|
|
||||||
qemu/qcow2/*
|
|
||||||
!qemu/qcow2/.keep
|
|
||||||
qemu/bin/*
|
|
||||||
!qemu/bin/.keep
|
|
||||||
share/*
|
|
||||||
!share/.keep
|
|
||||||
nix/local.nix
|
|
30
README.md
30
README.md
@ -4,9 +4,7 @@ Simple application VM's based on Nix package manager.
|
|||||||
|
|
||||||
Uses one **read-only** /nix directory for all appvms. So creating a new appvm (but not first) is just about one minute.
|
Uses one **read-only** /nix directory for all appvms. So creating a new appvm (but not first) is just about one minute.
|
||||||
|
|
||||||
Designed primarily for full screen usage (but remote-viewer has ability to resize window dynamically without change resolution) without guest additions (because of **less attack surface**).
|
Currently optimized for full screen usage (but remote-viewer has ability to resize window dynamically without change resolution) without guest additions.
|
||||||
|
|
||||||
It's a proof-of-concept, but you can still use it. Also there is a lot of strange things inside, don't afraid of :)
|
|
||||||
|
|
||||||
![appvm screenshot](screenshots/2018-07-05.png)
|
![appvm screenshot](screenshots/2018-07-05.png)
|
||||||
|
|
||||||
@ -19,41 +17,41 @@ It's a proof-of-concept, but you can still use it. Also there is a lot of strang
|
|||||||
|
|
||||||
$ su -c 'USE="spice virtfs" emerge qemu virt-manager'
|
$ su -c 'USE="spice virtfs" emerge qemu virt-manager'
|
||||||
|
|
||||||
## Add appvm to PATH
|
## Libvirt from user (required if you need access to shared files)
|
||||||
|
|
||||||
$ echo 'PATH=$PATH:$HOME/appvm/bin' >> ~/.bashrc
|
$ echo user = "$USER" | sudo tee -a /etc/libvirt/qemu.conf
|
||||||
|
|
||||||
(if you clone appvm to home directory)
|
## Install appvm tool
|
||||||
|
|
||||||
|
$ go get github.com/jollheef/appvm
|
||||||
|
|
||||||
## Generate resolution
|
## Generate resolution
|
||||||
|
|
||||||
By default uses 3840x2160. If you need to regenerate `appvm/nix/monitor.nix`:
|
By default uses 3840x2160. If you need to regenerate `appvm/nix/monitor.nix`:
|
||||||
|
|
||||||
$ appvm/appvm.sh generate-resolution 1920 1080 > appvm/nix/monitor.nix
|
$ $GOPATH/github.com/jollheef/appvm/generate-resolution.sh 1920 1080 > $GOPATH/github.com/jollheef/appvm/nix/monitor.nix
|
||||||
|
|
||||||
Autodetection is a bash-spaghetti, so you need to check results. BTW it's just a X.org monitor section.
|
Autodetection is a bash-spaghetti, so you need to check results. BTW it's just a X.org monitor section.
|
||||||
|
|
||||||
## Create VM
|
|
||||||
|
|
||||||
$ $HOME/appvm/appvm.sh build chromium
|
|
||||||
|
|
||||||
You can customize local settings in `nix/local.nix`.
|
|
||||||
|
|
||||||
## Run application
|
## Run application
|
||||||
|
|
||||||
$ appvm.chromium
|
($GOPATH/bin must be in $PATH)
|
||||||
|
|
||||||
|
$ appvm start chromium
|
||||||
|
|
||||||
|
You can customize local settings in `$GOPATH/github.com/jollheef/appvm/nix/local.nix`.
|
||||||
|
|
||||||
Default hotkey to release cursor: ctrl+alt.
|
Default hotkey to release cursor: ctrl+alt.
|
||||||
|
|
||||||
## Shared directory
|
## Shared directory
|
||||||
|
|
||||||
$ ls appvm/share/chromium
|
$ ls appvm/chromium
|
||||||
foo.tar.gz
|
foo.tar.gz
|
||||||
bar.tar.gz
|
bar.tar.gz
|
||||||
|
|
||||||
## Close VM
|
## Close VM
|
||||||
|
|
||||||
$ pkill.... :)
|
$ appvm stop chromium
|
||||||
|
|
||||||
# App description
|
# App description
|
||||||
|
|
||||||
|
212
appvm.go
Normal file
212
appvm.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* @author Mikhail Klementev jollheef<AT>riseup.net
|
||||||
|
* @license GNU GPLv3
|
||||||
|
* @date July 2018
|
||||||
|
* @brief appvm launcher
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/digitalocean/go-libvirt"
|
||||||
|
"github.com/jollheef/go-system"
|
||||||
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var xmlTmpl = `
|
||||||
|
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
|
||||||
|
<name>%s</name>
|
||||||
|
<memory unit='KiB'>1048576</memory>
|
||||||
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
||||||
|
<vcpu placement='static'>1</vcpu>
|
||||||
|
<os>
|
||||||
|
<type arch='x86_64' machine='pc-i440fx-2.12'>hvm</type>
|
||||||
|
<kernel>%s/kernel</kernel>
|
||||||
|
<initrd>%s/initrd</initrd>
|
||||||
|
<cmdline>loglevel=4 init=%s/init %s</cmdline>
|
||||||
|
</os>
|
||||||
|
<features>
|
||||||
|
<acpi/>
|
||||||
|
</features>
|
||||||
|
<clock offset='utc'/>
|
||||||
|
<on_poweroff>destroy</on_poweroff>
|
||||||
|
<on_reboot>restart</on_reboot>
|
||||||
|
<on_crash>destroy</on_crash>
|
||||||
|
<devices>
|
||||||
|
<!-- Graphical console -->
|
||||||
|
<graphics type='spice' autoport='yes'>
|
||||||
|
<listen type='address'/>
|
||||||
|
<image compression='off'/>
|
||||||
|
</graphics>
|
||||||
|
<!-- Fake (because -snapshot) writeback image -->
|
||||||
|
<disk type='file' device='disk'>
|
||||||
|
<driver name='qemu' type='qcow2' cache='writeback' error_policy='report'/>
|
||||||
|
<source file='%s'/>
|
||||||
|
<target dev='vda' bus='virtio'/>
|
||||||
|
</disk>
|
||||||
|
<video>
|
||||||
|
<model type='qxl' ram='524288' vram='524288' vgamem='262144' heads='1' primary='yes'/>
|
||||||
|
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
|
||||||
|
</video>
|
||||||
|
<!-- filesystems -->
|
||||||
|
<filesystem type='mount' accessmode='passthrough'>
|
||||||
|
<source dir='/nix/store'/>
|
||||||
|
<target dir='store'/>
|
||||||
|
<readonly/>
|
||||||
|
</filesystem>
|
||||||
|
<filesystem type='mount' accessmode='mapped'>
|
||||||
|
<source dir='%s'/>
|
||||||
|
<target dir='xchg'/> <!-- workaround for nixpkgs/nixos/modules/virtualisation/qemu-vm.nix -->
|
||||||
|
</filesystem>
|
||||||
|
<filesystem type='mount' accessmode='mapped'>
|
||||||
|
<source dir='%s'/>
|
||||||
|
<target dir='shared'/> <!-- workaround for nixpkgs/nixos/modules/virtualisation/qemu-vm.nix -->
|
||||||
|
</filesystem>
|
||||||
|
<filesystem type='mount' accessmode='mapped'>
|
||||||
|
<source dir='%s'/>
|
||||||
|
<target dir='home'/>
|
||||||
|
</filesystem>
|
||||||
|
</devices>
|
||||||
|
<qemu:commandline>
|
||||||
|
<qemu:arg value='-device'/>
|
||||||
|
<qemu:arg value='e1000,netdev=net0'/>
|
||||||
|
<qemu:arg value='-netdev'/>
|
||||||
|
<qemu:arg value='user,id=net0'/>
|
||||||
|
<qemu:arg value='-snapshot'/>
|
||||||
|
</qemu:commandline>
|
||||||
|
</domain>
|
||||||
|
`
|
||||||
|
|
||||||
|
func generateXML(name, vmNixPath, reginfo, img, sharedDir string) string {
|
||||||
|
// TODO: Define XML in go
|
||||||
|
return fmt.Sprintf(xmlTmpl, "appvm_"+name, vmNixPath, vmNixPath, vmNixPath,
|
||||||
|
reginfo, img, sharedDir, sharedDir, sharedDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(l *libvirt.Libvirt) {
|
||||||
|
domains, err := l.Domains()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO list available to create VM's too
|
||||||
|
for _, d := range domains {
|
||||||
|
if d.Name[0:5] == "appvm" {
|
||||||
|
fmt.Println(d.Name[6:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(l *libvirt.Libvirt, name string) {
|
||||||
|
// Currently binary-only installation is not supported, because we need *.nix configurations
|
||||||
|
gopath := os.Getenv("GOPATH")
|
||||||
|
err := os.Chdir(gopath + "/src/github.com/jollheef/appvm")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, err = system.System("nix-build", "<nixpkgs/nixos>", "-A", "config.system.build.vm",
|
||||||
|
"-I", "nixos-config=nix/"+name+".nix", "-I", ".")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
realpath, err := filepath.EvalSymlinks("result/system")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use go regex
|
||||||
|
reginfo, _, _, err := system.System("sh", "-c", "cat result/bin/run-nixos-vm | grep -o 'regInfo=.*/registration'")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
syscall.Unlink("result")
|
||||||
|
|
||||||
|
qcow2 := "/tmp/.appvm.fake.qcow2"
|
||||||
|
if _, err := os.Stat(qcow2); os.IsNotExist(err) {
|
||||||
|
system.System("qemu-img", "create", "-f", "qcow2", qcow2, "512M")
|
||||||
|
err := os.Chmod(qcow2, 0400) // qemu run with -snapshot, we only need it for create /dev/vda
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedDir := fmt.Sprintf(os.Getenv("HOME") + "/appvm/" + name)
|
||||||
|
os.MkdirAll(sharedDir, 0700)
|
||||||
|
|
||||||
|
// TODO: Search go libraries for manipulate ACL
|
||||||
|
_, _, _, err = system.System("setfacl", "-R", "-m", "u:qemu:rwx", os.Getenv("HOME")+"/appvm/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
xml := generateXML(name, realpath, reginfo, qcow2, sharedDir)
|
||||||
|
_, err = l.DomainCreateXML(xml, libvirt.DomainStartValidate)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("virt-viewer", "-f", "appvm_"+name)
|
||||||
|
cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(l *libvirt.Libvirt, name string) {
|
||||||
|
dom, err := l.DomainLookupByName("appvm_" + name)
|
||||||
|
if err != nil {
|
||||||
|
if libvirt.IsNotFound(err) {
|
||||||
|
log.Println("Appvm not found or already stopped")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = l.DomainDestroy(dom)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drop(name string) {
|
||||||
|
appDataPath := fmt.Sprintf(os.Getenv("HOME") + "/appvm/" + name)
|
||||||
|
os.RemoveAll(appDataPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", time.Second)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l := libvirt.New(c)
|
||||||
|
if err := l.Connect(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Disconnect()
|
||||||
|
|
||||||
|
kingpin.Command("list", "List applications")
|
||||||
|
startName := kingpin.Command("start", "Start application").Arg("name", "Application name").Required().String()
|
||||||
|
stopName := kingpin.Command("stop", "Stop application").Arg("name", "Application name").Required().String()
|
||||||
|
dropName := kingpin.Command("drop", "Remove application data").Arg("name", "Application name").Required().String()
|
||||||
|
|
||||||
|
switch kingpin.Parse() {
|
||||||
|
case "list":
|
||||||
|
list(l)
|
||||||
|
case "start":
|
||||||
|
start(l, *startName)
|
||||||
|
case "stop":
|
||||||
|
stop(l, *stopName)
|
||||||
|
case "drop":
|
||||||
|
drop(*dropName)
|
||||||
|
}
|
||||||
|
}
|
49
appvm.sh
49
appvm.sh
@ -1,49 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
APPVM_PATH=$(dirname $(realpath $0))
|
|
||||||
cd ${APPVM_PATH}
|
|
||||||
|
|
||||||
if [ ! -f nix/local.nix ]; then
|
|
||||||
echo "[*] There is no local.nix, creating."
|
|
||||||
echo -e "{\n}" >> nix/local.nix
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$1" == "build" && "$2" != "" ]]; then
|
|
||||||
if [ -f bin/appvm.${2} ]; then
|
|
||||||
echo "[*] Kill app."
|
|
||||||
pkill -f "$(cat bin/appvm.${2} | grep pgrep | awk '{ print $3 }')"
|
|
||||||
fi
|
|
||||||
if [ -f qemu/qcow2/${2}.qcow2 ]; then
|
|
||||||
echo "[*] Remove old app state."
|
|
||||||
rm qemu/qcow2/${2}.qcow2
|
|
||||||
fi
|
|
||||||
NIX_PATH=$NIX_PATH:. nix-build '<nixpkgs/nixos>' -A config.system.build.vm -I nixos-config=nix/${2}.nix || exit 1
|
|
||||||
NIX_SYSTEM=$(realpath result/system)
|
|
||||||
mkdir -p bin
|
|
||||||
RAND_HASH=$(head /dev/urandom | md5sum | awk '{ print $1 }')
|
|
||||||
VM_BIN_PATH=$(realpath qemu/bin/qemu.${RAND_HASH}.${2})
|
|
||||||
sed "s;NIX_SYSTEM_PLACEHOLDER;${NIX_SYSTEM};" qemu/qemu.template > ${VM_BIN_PATH}
|
|
||||||
sed -i "s;NAME_PLACEHOLDER;${2};" ${VM_BIN_PATH}
|
|
||||||
sed -i "s;HASH_PLACEHOLDER;${RAND_HASH};" ${VM_BIN_PATH}
|
|
||||||
sed -i "s;NIX_DISK_IMAGE_PLACEHOLDER;${APPVM_PATH}/qemu/qcow2/${2}.qcow2;" ${VM_BIN_PATH}
|
|
||||||
RANDOM_PORT=$(/usr/bin/python -c 'import random; print(random.randint(1024,65535))')
|
|
||||||
# TODO Check for port collisions
|
|
||||||
sed -i "s;PORT_PLACEHOLDER;${RANDOM_PORT};" ${VM_BIN_PATH}
|
|
||||||
echo -e "#!/bin/bash\npgrep -f ${RAND_HASH} || {\n\tnohup setsid ${VM_BIN_PATH} >/dev/null 2>&1 &\n\tsleep 1s\n}\nremote-viewer -f spice://127.200.0.1:${RANDOM_PORT}" > bin/appvm.${2}
|
|
||||||
chmod +x ${VM_BIN_PATH}
|
|
||||||
chmod +x bin/appvm.${2}
|
|
||||||
unlink result
|
|
||||||
elif [[ "$1" == "generate-resolution" && "$2" != "" && "$3" != "" ]]; then
|
|
||||||
MONITOR_SIZE="$(xrandr | grep mm | head -n 1 | awk '{ print $(NF-2) " " $(NF) }' | sed 's/mm//g')"
|
|
||||||
CVT="$(cvt ${2} ${3} | grep Modeline)"
|
|
||||||
echo "{"
|
|
||||||
echo " services.xserver.monitorSection = ''"
|
|
||||||
echo " " ${CVT}
|
|
||||||
echo " " Option '"PreferredMode"' $(echo ${CVT} | awk '{ print $2 }')
|
|
||||||
echo " " DisplaySize ${MONITOR_SIZE} # In millimeters
|
|
||||||
echo " '';"
|
|
||||||
echo "}"
|
|
||||||
else
|
|
||||||
echo -e "Usage:\t$0 build APPLICATION"
|
|
||||||
echo -e "or:\t$0 generate-resolution X Y"
|
|
||||||
fi
|
|
16
generate-resolution.sh
Executable file
16
generate-resolution.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [[ "$1" == "" || "$2" == "" ]]; then
|
||||||
|
echo -e "Usage:\t$0 X Y"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MONITOR_SIZE="$(xrandr | grep mm | head -n 1 | awk '{ print $(NF-2) " " $(NF) }' | sed 's/mm//g')"
|
||||||
|
CVT="$(cvt ${1} ${2} | grep Modeline)"
|
||||||
|
echo "{"
|
||||||
|
echo " services.xserver.monitorSection = ''"
|
||||||
|
echo " " ${CVT}
|
||||||
|
echo " " Option '"PreferredMode"' $(echo ${CVT} | awk '{ print $2 }')
|
||||||
|
echo " " DisplaySize ${MONITOR_SIZE} # In millimeters
|
||||||
|
echo " '';"
|
||||||
|
echo "}"
|
13
nix/base.nix
13
nix/base.nix
@ -36,11 +36,22 @@ main = xmonad defaultConfig
|
|||||||
description = "Create and xmonad configuration";
|
description = "Create and xmonad configuration";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ConditionFileNotEmpty = "!/home/user/.xmonad/xmonad.hs";
|
ConditionFileNotEmpty = "!/home/user/.xmonad/xmonad.hs";
|
||||||
ExecStart = "/bin/sh -c 'mkdir /home/user/.xmonad && cp /etc/xmonad.hs /home/user/.xmonad/xmonad.hs'";
|
ExecStart = "/bin/sh -c 'mkdir -p /home/user/.xmonad && cp /etc/xmonad.hs /home/user/.xmonad/xmonad.hs'";
|
||||||
RemainAfterExit = "yes";
|
RemainAfterExit = "yes";
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
User = "user";
|
User = "user";
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.mount-home-user = {
|
||||||
|
description = "Mount /home/user (crutch)";
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "/bin/sh -c '/run/current-system/sw/bin/mount -t 9p -o trans=virtio,version=9p2000.L,uid=1000 home /home/user'";
|
||||||
|
RemainAfterExit = "yes";
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "root";
|
||||||
|
};
|
||||||
|
wantedBy = [ "sysinit.target" ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
4
nix/local.nix
Normal file
4
nix/local.nix
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
services.xserver.layout = "us,ru";
|
||||||
|
services.xserver.xkbOptions = "ctrl:nocaps,grp:rctrl_toggle";
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
NAME=NAME_PLACEHOLDER
|
|
||||||
|
|
||||||
NIX_DISK_IMAGE=NIX_DISK_IMAGE_PLACEHOLDER
|
|
||||||
|
|
||||||
if ! test -e "$NIX_DISK_IMAGE"; then
|
|
||||||
qemu-img create -f qcow2 "$NIX_DISK_IMAGE" 512M || exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a directory for storing temporary data of the running VM.
|
|
||||||
TMPDIR=$(dirname ${NIX_DISK_IMAGE})/../../share/NAME_PLACEHOLDER
|
|
||||||
|
|
||||||
# Create a directory for exchanging data with the VM.
|
|
||||||
mkdir -p $TMPDIR
|
|
||||||
cd $TMPDIR
|
|
||||||
|
|
||||||
NIX_SYSTEM="NIX_SYSTEM_PLACEHOLDER"
|
|
||||||
|
|
||||||
# Start QEMU.
|
|
||||||
qemu-system-x86_64 -enable-kvm \
|
|
||||||
-name NAME_PLACEHOLDER_HASH_PLACEHOLDER \
|
|
||||||
-m 1024 \
|
|
||||||
-smp 1 \
|
|
||||||
-device virtio-rng-pci \
|
|
||||||
-net nic,netdev=user.0,model=virtio -netdev user,id=user.0${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} \
|
|
||||||
-spice port=PORT_PLACEHOLDER,addr=127.200.0.1,disable-ticketing,image-compression=off,seamless-migration=on \
|
|
||||||
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
|
|
||||||
-virtfs local,path=/nix/store,security_model=none,mount_tag=store,readonly \
|
|
||||||
-virtfs local,path=$TMPDIR,security_model=none,mount_tag=xchg \
|
|
||||||
-virtfs local,path=${SHARED_DIR:-$TMPDIR},security_model=none,mount_tag=shared \
|
|
||||||
-drive index=0,id=drive$((0 + 1)),file=$NIX_DISK_IMAGE,cache=writeback,werror=report,if=virtio \
|
|
||||||
-kernel ${NIX_SYSTEM}/kernel \
|
|
||||||
-initrd ${NIX_SYSTEM}/initrd \
|
|
||||||
-append "$(cat ${NIX_SYSTEM}/kernel-params) init=${NIX_SYSTEM}/init regInfo=/nix/store/622pn30mg7z4knkrqsh3acrjyaiyq6sr-closure-info/registration" \
|
|
||||||
-device qxl-vga,vgamem_mb=256 #-display gtk
|
|
Loading…
Reference in New Issue
Block a user