Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
40ef3fe50e
|
|||
d973179557
|
|||
49b8790032
|
|||
355fb314a1
|
|||
e037770c38
|
|||
57e15fa0a0
|
|||
f1fd2e1505
|
|||
02dda8bcf9
|
|||
13226a6a79
|
|||
0c35a66606
|
|||
dbfc2929db
|
|||
3e8a08d638
|
|||
8baa1ff73b
|
|||
25a2f45e59
|
|||
af5691b0d4
|
|||
6c72b5de00
|
|||
04b2cf63ce
|
|||
7e4aa33a0a
|
|||
bc704df503
|
|||
43d42c2242
|
|||
f4c6a6a90b
|
|||
0a51db3bde
|
|||
99bd71b80c
|
|||
3416808444
|
|||
9c6b8a0122
|
|||
cc92ec3e23
|
|||
6ebc562599
|
|||
f18d55bd27
|
|||
197a78f595 | |||
555768d03b | |||
4104d91eab | |||
2d5b72219f
|
|||
84095193e5
|
|||
718ab3a164
|
|||
6b35acf673
|
|||
38a3ba301b
|
|||
29e447435f
|
|||
8a93015bfe
|
|||
e7cc81694b
|
|||
6505544535
|
|||
24927bc787
|
|||
1251a21aba
|
|||
e5ccd6d913
|
|||
0823b2f028 | |||
4ce9b6ee86 | |||
ba569d4aab | |||
302d004e85 | |||
55a825a7c0
|
|||
1270b2e209
|
|||
870c9d6da8
|
|||
bd8d6d822d
|
|||
23947390a1
|
|||
6a54439180
|
|||
959e6e6596
|
|||
8bcf3eae87
|
|||
bb5a352215
|
|||
660c8a5dba
|
|||
9ac5d98ddb
|
|||
20b9b42e03 | |||
fd224db22e
|
|||
f60945d3c1
|
|||
575e147989
|
|||
ff4e7f8398
|
|||
9218a10641
|
|||
326b9ce610 | |||
36267ac5ea | |||
5bb305a0ec | |||
5f81e7ac3d | |||
cb343c4506 | |||
02f4eab277 |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
Makes sure these boxes are checked before submitting your pull request -- thank you!
|
||||
|
||||
- [ ] I tested it locally.
|
||||
- [ ] I tried to run at least one application VM and it works.
|
16
.github/workflows/macos.yml
vendored
Normal file
16
.github/workflows/macos.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: macOS
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: go get -d ./...
|
||||
|
||||
- name: Build
|
||||
run: go build
|
16
.github/workflows/ubuntu.yml
vendored
Normal file
16
.github/workflows/ubuntu.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: Ubuntu
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: go get -d ./...
|
||||
|
||||
- name: Build
|
||||
run: go build
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
nix/local.nix
|
||||
nix/monitor.nix
|
50
README.md
50
README.md
@ -1,11 +1,12 @@
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R8W2UQPZ5X5JE&source=url)
|
||||
[](https://blockchair.com/bitcoin/address/bc1q23fyuq7kmngrgqgp6yq9hk8a5q460f39m8nv87)
|
||||
|
||||
# Nix application VMs: security through virtualization
|
||||
|
||||
Simple application VMs (hypervisor-based sandbox) 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.
|
||||
|
||||
Currently optimized for full screen usage (but remote-viewer has ability to resize window dynamically without change resolution).
|
||||
|
||||

|
||||
|
||||
## Dependencies
|
||||
@ -33,26 +34,32 @@ You need to **relogin** if you install virt-manager (libvirt) first time.
|
||||
|
||||
## Install appvm tool
|
||||
|
||||
$ go get github.com/jollheef/appvm
|
||||
$ go get code.dumpstack.io/tools/appvm
|
||||
|
||||
## Update appvm tool
|
||||
|
||||
$ go get -u github.com/jollheef/appvm
|
||||
$ go get -u code.dumpstack.io/tools/appvm
|
||||
|
||||
## Generate resolution
|
||||
## Search for applications
|
||||
|
||||
By default uses 1920x1080. If you need to regenerate `appvm/nix/monitor.nix`:
|
||||
$ appvm search chromium
|
||||
|
||||
$ $GOPATH/src/github.com/jollheef/appvm/generate-resolution.sh 3840 2160 > $GOPATH/src/github.com/jollheef/appvm/nix/monitor.nix
|
||||
## Generate new application
|
||||
|
||||
Autodetection is a bash-spaghetti, so you need to check results. BTW it's just a X.org monitor section.
|
||||
$ nix-channel --list
|
||||
nix https://nixos.org/channels/nixos-unstable
|
||||
$ appvm generate nix.firefox
|
||||
|
||||
## Run application
|
||||
|
||||
$ appvm start chromium
|
||||
$ # ... long wait for first time, because we need to collect a lot of packages
|
||||
|
||||
You can customize local settings in `$GOPATH/github.com/jollheef/appvm/nix/local.nix`.
|
||||
## Synchronize remote repos for applications
|
||||
|
||||
$ appvm sync
|
||||
|
||||
You can customize local settings in **~/.config/appvm/nix/local.nix**.
|
||||
|
||||
Default hotkey to release cursor: ctrl+alt.
|
||||
|
||||
@ -76,28 +83,3 @@ to crontab like that:
|
||||
|
||||
$ crontab -l
|
||||
* * * * * /home/user/dev/go/bin/appvm autoballoon
|
||||
|
||||
# App description
|
||||
|
||||
$ cat nix/chromium.nix
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.chromium ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.chromium}/bin/chromium; done &";
|
||||
}
|
||||
|
||||
For create new app you should add package name (search at https://nixos.org/nixos/packages.html) and path to binary (typically same as package name).
|
||||
|
||||
## Defined applications (pull requests are welcome!)
|
||||
|
||||
* chromium
|
||||
* thunderbird
|
||||
* tdesktop
|
||||
* evince
|
||||
* libreoffice
|
||||
* wire
|
||||
|
415
appvm.go
415
appvm.go
@ -8,98 +8,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
"github.com/go-cmd/cmd"
|
||||
"github.com/jollheef/go-system"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
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='GiB'>2</memory>
|
||||
<currentMemory unit='GiB'>1</currentMemory>
|
||||
<vcpu>4</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>
|
||||
<!-- Guest additionals support -->
|
||||
<channel type='spicevmc'>
|
||||
<target type='virtio' name='com.redhat.spice.0'/>
|
||||
</channel>
|
||||
<!-- 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 {
|
||||
@ -108,23 +39,27 @@ func list(l *libvirt.Libvirt) {
|
||||
|
||||
fmt.Println("Started VM:")
|
||||
for _, d := range domains {
|
||||
if d.Name[0:5] == "appvm" {
|
||||
if strings.HasPrefix(d.Name, "appvm") {
|
||||
fmt.Println("\t", d.Name[6:])
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nAvailable VM:")
|
||||
files, err := ioutil.ReadDir(os.Getenv("GOPATH") + "/src/github.com/jollheef/appvm/nix")
|
||||
files, err := ioutil.ReadDir(configDir + "/nix")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if f.Name() != "base.nix" &&
|
||||
f.Name() != "local.nix" && f.Name() != "monitor.nix" &&
|
||||
f.Name() != "local.nix.template" && f.Name() != "monitor.nix.template" {
|
||||
fmt.Println("\t", f.Name()[0:len(f.Name())-4])
|
||||
switch f.Name() {
|
||||
case "base.nix":
|
||||
continue
|
||||
case "local.nix":
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
fmt.Println("\t", f.Name()[0:len(f.Name())-4])
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,74 +84,202 @@ func copyFile(from, to string) (err error) {
|
||||
return destination.Close()
|
||||
}
|
||||
|
||||
func start(l *libvirt.Libvirt, name string) {
|
||||
// Currently binary-only installation is not supported, because we need *.nix configurations
|
||||
gopath := os.Getenv("GOPATH")
|
||||
appvmPath := gopath + "/src/github.com/jollheef/appvm"
|
||||
err := os.Chdir(appvmPath)
|
||||
func prepareTemplates(appvmPath string) (err error) {
|
||||
if _, err = os.Stat(appvmPath + "/nix/local.nix"); os.IsNotExist(err) {
|
||||
err = ioutil.WriteFile(configDir+"/nix/local.nix", local_nix_template, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Copy templates
|
||||
if _, err := os.Stat(appvmPath + "/nix/local.nix"); os.IsNotExist(err) {
|
||||
err = copyFile(appvmPath+"/nix/local.nix.template", appvmPath+"/nix/local.nix")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(appvmPath + "/nix/monitor.nix"); os.IsNotExist(err) {
|
||||
err = copyFile(appvmPath+"/nix/monitor.nix.template", appvmPath+"/nix/monitor.nix")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
func streamStdOutErr(command *cmd.Cmd) {
|
||||
for {
|
||||
select {
|
||||
case line := <-command.Stdout:
|
||||
fmt.Println(line)
|
||||
case line := <-command.Stderr:
|
||||
fmt.Fprintln(os.Stderr, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout, stderr, ret, err := system.System("nix-build", "<nixpkgs/nixos>", "-A", "config.system.build.vm",
|
||||
func generateVM(name string, verbose bool) (realpath, reginfo, qcow2 string, err error) {
|
||||
command := cmd.NewCmdOptions(cmd.Options{Buffered: false, Streaming: true},
|
||||
"nix-build", "<nixpkgs/nixos>", "-A", "config.system.build.vm",
|
||||
"-I", "nixos-config=nix/"+name+".nix", "-I", ".")
|
||||
if err != nil {
|
||||
log.Fatalln(err, stdout, stderr, ret)
|
||||
|
||||
if verbose {
|
||||
go streamStdOutErr(command)
|
||||
}
|
||||
|
||||
realpath, err := filepath.EvalSymlinks("result/system")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
status := <-command.Start()
|
||||
if status.Error != nil || status.Exit != 0 {
|
||||
log.Println(status.Error, status.Stdout, status.Stderr)
|
||||
if status.Error != nil {
|
||||
err = status.Error
|
||||
} else {
|
||||
s := fmt.Sprintf("ret code: %d, out: %v, err: %v",
|
||||
status.Exit, status.Stdout, status.Stderr)
|
||||
err = errors.New(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Use go regex
|
||||
reginfo, _, _, err := system.System("sh", "-c", "cat result/bin/run-nixos-vm | grep -o 'regInfo=.*/registration'")
|
||||
realpath, err = filepath.EvalSymlinks("result/system")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadFile("result/bin/run-nixos-vm")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
match := regexp.MustCompile("regInfo=.*/registration").FindSubmatch(bytes)
|
||||
if len(match) != 1 {
|
||||
err = errors.New("should be one reginfo")
|
||||
return
|
||||
}
|
||||
|
||||
reginfo = string(match[0])
|
||||
|
||||
syscall.Unlink("result")
|
||||
|
||||
qcow2 := "/tmp/.appvm.fake.qcow2"
|
||||
if _, err := os.Stat(qcow2); os.IsNotExist(err) {
|
||||
qcow2 = os.Getenv("HOME") + "/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
|
||||
err = os.Chmod(qcow2, 0400) // qemu run with -snapshot, we only need it for create /dev/vda
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sharedDir := fmt.Sprintf(os.Getenv("HOME") + "/appvm/" + name)
|
||||
return
|
||||
}
|
||||
|
||||
func isRunning(l *libvirt.Libvirt, name string) bool {
|
||||
_, err := l.DomainLookupByName("appvm_" + name) // yep, there is no libvirt error handling
|
||||
// VM is destroyed when stop so NO VM means STOPPED
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func generateAppVM(l *libvirt.Libvirt,
|
||||
nixName, vmName, appvmPath, sharedDir string,
|
||||
verbose, online bool) (err error) {
|
||||
|
||||
err = os.Chdir(appvmPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
realpath, reginfo, qcow2, err := generateVM(nixName, verbose)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
xml := generateXML(vmName, online, realpath, reginfo, qcow2, sharedDir)
|
||||
_, err = l.DomainCreateXML(xml, libvirt.DomainStartValidate)
|
||||
return
|
||||
}
|
||||
|
||||
func stupidProgressBar() {
|
||||
const length = 70
|
||||
for {
|
||||
time.Sleep(time.Second / 4)
|
||||
fmt.Printf("\r%s]\r[", strings.Repeat(" ", length))
|
||||
for i := 0; i <= length-2; i++ {
|
||||
time.Sleep(time.Second / 20)
|
||||
fmt.Printf("+")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
func isAppvmConfigurationExists(appvmPath, name string) bool {
|
||||
return fileExists(appvmPath + "/nix/" + name + ".nix")
|
||||
}
|
||||
|
||||
func start(l *libvirt.Libvirt, name string, verbose, online, stateless bool,
|
||||
args, open string) {
|
||||
|
||||
appvmPath := configDir
|
||||
|
||||
statelessName := fmt.Sprintf("tmp_%d_%s", rand.Int(), name)
|
||||
|
||||
sharedDir := os.Getenv("HOME") + "/appvm/"
|
||||
if stateless {
|
||||
sharedDir += statelessName
|
||||
} else {
|
||||
sharedDir += 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/")
|
||||
vmName := "appvm_"
|
||||
if stateless {
|
||||
vmName += statelessName
|
||||
} else {
|
||||
vmName += name
|
||||
}
|
||||
|
||||
if open != "" {
|
||||
filename := sharedDir + "/" + filepath.Base(open)
|
||||
err := copyFile(open, filename)
|
||||
if err != nil {
|
||||
log.Println("Can't copy file")
|
||||
return
|
||||
}
|
||||
|
||||
args += "/home/user/" + filepath.Base(open)
|
||||
}
|
||||
|
||||
if args != "" {
|
||||
err := ioutil.WriteFile(sharedDir+"/"+".args", []byte(args), 0700)
|
||||
if err != nil {
|
||||
log.Println("Can't write args")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !isAppvmConfigurationExists(appvmPath, name) {
|
||||
log.Println("No configuration exists for app, " +
|
||||
"trying to generate")
|
||||
err := generate(l, name, "", "")
|
||||
if err != nil {
|
||||
log.Println("Can't auto generate")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Copy templates
|
||||
err := prepareTemplates(appvmPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
xml := generateXML(name, realpath, reginfo, qcow2, sharedDir)
|
||||
_, err = l.DomainCreateXML(xml, libvirt.DomainStartValidate)
|
||||
if !isRunning(l, vmName) {
|
||||
if !verbose {
|
||||
go stupidProgressBar()
|
||||
}
|
||||
|
||||
err = generateAppVM(l, name, vmName, appvmPath, sharedDir,
|
||||
verbose, online)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("virt-viewer", "appvm_"+name)
|
||||
cmd := exec.Command("virt-viewer", "-c", "qemu:///system", vmName)
|
||||
cmd.Start()
|
||||
}
|
||||
|
||||
@ -241,18 +304,16 @@ func drop(name string) {
|
||||
os.RemoveAll(appDataPath)
|
||||
}
|
||||
|
||||
func autoBalloon(l *libvirt.Libvirt) {
|
||||
func autoBalloon(l *libvirt.Libvirt, memoryMin, adjustPercent uint64) {
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
const memoryMin = 512 * 1024 // 512 MiB
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Application VM", "Used memory", "Current memory", "Max memory", "New memory"})
|
||||
for _, d := range domains {
|
||||
if d.Name[0:5] == "appvm" {
|
||||
if strings.HasPrefix(d.Name, "appvm_") {
|
||||
name := d.Name[6:]
|
||||
|
||||
memoryUsedRaw, err := ioutil.ReadFile(os.Getenv("HOME") + "/appvm/" + name + "/.memory_used")
|
||||
@ -270,7 +331,7 @@ func autoBalloon(l *libvirt.Libvirt) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
memoryNew := uint64(float64(memoryUsed) * 1.2) // +20%
|
||||
memoryNew := uint64(float64(memoryUsed) * (1 + float64(adjustPercent)/100))
|
||||
|
||||
if memoryNew > memoryMax {
|
||||
memoryNew = memoryMax - 1
|
||||
@ -295,7 +356,81 @@ func autoBalloon(l *libvirt.Libvirt) {
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func search(name string) {
|
||||
command := exec.Command("nix", "search", name)
|
||||
bytes, err := command.Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(bytes), "\n") {
|
||||
fmt.Println(line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sync() {
|
||||
err := exec.Command("nix-channel", "--update").Run()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = exec.Command("nix", "search", "-u").Run()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Println("Done")
|
||||
}
|
||||
|
||||
func cleanupStatelessVMs(l *libvirt.Libvirt) {
|
||||
domains, err := l.Domains()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(appvmHomesDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, f := range dirs {
|
||||
if !strings.HasPrefix(f.Name(), "tmp_") {
|
||||
continue
|
||||
}
|
||||
|
||||
alive := false
|
||||
for _, d := range domains {
|
||||
if d.Name == "appvm_"+f.Name() {
|
||||
alive = true
|
||||
}
|
||||
}
|
||||
if !alive {
|
||||
os.RemoveAll(appvmHomesDir + f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var configDir = os.Getenv("HOME") + "/.config/appvm/"
|
||||
var appvmHomesDir = os.Getenv("HOME") + "/appvm/"
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
os.Mkdir(os.Getenv("HOME")+"/appvm", 0700)
|
||||
|
||||
os.MkdirAll(configDir+"/nix", 0700)
|
||||
|
||||
err := writeBuiltinApps(configDir + "/nix")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(configDir+"/nix/base.nix", base_nix, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", time.Second)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -307,22 +442,52 @@ func main() {
|
||||
}
|
||||
defer l.Disconnect()
|
||||
|
||||
cleanupStatelessVMs(l)
|
||||
|
||||
kingpin.Command("list", "List applications")
|
||||
kingpin.Command("autoballoon", "Automatically adjust/reduce app vm memory")
|
||||
startName := kingpin.Command("start", "Start application").Arg("name", "Application name").Required().String()
|
||||
autoballonCommand := kingpin.Command("autoballoon", "Automatically adjust/reduce app vm memory")
|
||||
minMemory := autoballonCommand.Flag("min-memory", "Set minimal memory (megabytes)").Default("1024").Uint64()
|
||||
adjustPercent := autoballonCommand.Flag("adj-memory", "Adjust memory amount (percents)").Default("20").Uint64()
|
||||
|
||||
startCommand := kingpin.Command("start", "Start application")
|
||||
startName := startCommand.Arg("name", "Application name").Required().String()
|
||||
startQuiet := startCommand.Flag("quiet", "Less verbosity").Bool()
|
||||
startArgs := startCommand.Flag("args", "Command line arguments").String()
|
||||
startOpen := startCommand.Flag("open", "Pass file to application").String()
|
||||
startOffline := startCommand.Flag("offline", "Disconnect").Bool()
|
||||
startStateless := startCommand.Flag("stateless", "Do not use default state directory").Bool()
|
||||
|
||||
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()
|
||||
|
||||
generateCommand := kingpin.Command("generate", "Generate appvm definition")
|
||||
generateName := generateCommand.Arg("name", "Nix package name").Required().String()
|
||||
generateBin := generateCommand.Arg("bin", "Binary").Default("").String()
|
||||
generateVMName := generateCommand.Flag("vm", "Use VM Name").Default("").String()
|
||||
|
||||
searchCommand := kingpin.Command("search", "Search for application")
|
||||
searchName := searchCommand.Arg("name", "Application name").Required().String()
|
||||
|
||||
kingpin.Command("sync", "Synchronize remote repos for applications")
|
||||
|
||||
switch kingpin.Parse() {
|
||||
case "list":
|
||||
list(l)
|
||||
case "search":
|
||||
search(*searchName)
|
||||
case "generate":
|
||||
generate(l, *generateName, *generateBin, *generateVMName)
|
||||
case "start":
|
||||
start(l, *startName)
|
||||
start(l, *startName,
|
||||
!*startQuiet, !*startOffline, *startStateless,
|
||||
*startArgs, *startOpen)
|
||||
case "stop":
|
||||
stop(l, *stopName)
|
||||
case "drop":
|
||||
drop(*dropName)
|
||||
case "autoballoon":
|
||||
autoBalloon(l)
|
||||
autoBalloon(l, *minMemory*1024, *adjustPercent)
|
||||
case "sync":
|
||||
sync()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
var base_nix = []byte(`
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nix/monitor.nix>
|
||||
<nix/local.nix>
|
||||
];
|
||||
|
||||
system.nixos.stateVersion = "18.03";
|
||||
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
desktopManager.xterm.enable = false;
|
||||
@ -37,25 +37,18 @@ main = xmonad defaultConfig
|
||||
|
||||
startup :: X ()
|
||||
startup = do
|
||||
spawn "spice-vdagent"
|
||||
spawn "while [ 1 ]; do ${pkgs.spice-vdagent}/bin/spice-vdagent -x; done &"
|
||||
'';
|
||||
|
||||
environment.systemPackages = [ pkgs.bc ];
|
||||
services.cron = {
|
||||
enable = true;
|
||||
systemCronJobs = [
|
||||
"* * * * * root free -m | grep Mem | awk '{print $2 \"-\" $4}' | bc > /home/user/.memory_used"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.home-user-build-xmonad = {
|
||||
description = "Create and xmonad configuration";
|
||||
description = "Link xmonad configuration";
|
||||
serviceConfig = {
|
||||
ConditionFileNotEmpty = "!/home/user/.xmonad/xmonad.hs";
|
||||
ExecStart = "/bin/sh -c 'mkdir -p /home/user/.xmonad && ln -s /etc/xmonad.hs /home/user/.xmonad/xmonad.hs'";
|
||||
RemainAfterExit = "yes";
|
||||
Type = "oneshot";
|
||||
User = "user";
|
||||
Restart = "on-failure";
|
||||
TimeoutSec = 10;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
@ -70,4 +63,45 @@ startup = do
|
||||
};
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
};
|
||||
|
||||
systemd.user.services."xrandr" = {
|
||||
serviceConfig = {
|
||||
StartLimitBurst = 100;
|
||||
};
|
||||
script = "${pkgs.xorg.xrandr}/bin/xrandr --output Virtual-1 --mode $(${pkgs.xorg.xrandr}/bin/xrandr | grep ' ' | head -n 2 | tail -n 1 | ${pkgs.gawk}/bin/awk '{ print $1 }')";
|
||||
};
|
||||
|
||||
systemd.user.timers."xrandr" = {
|
||||
description = "Auto update resolution crutch";
|
||||
timerConfig = {
|
||||
OnBootSec = "1s";
|
||||
OnUnitInactiveSec = "1s";
|
||||
Unit = "xrandr.service";
|
||||
AccuracySec = "1us";
|
||||
};
|
||||
wantedBy = ["timers.target"];
|
||||
};
|
||||
|
||||
systemd.services."autoballoon" = {
|
||||
serviceConfig = {
|
||||
StartLimitBurst = 100;
|
||||
};
|
||||
script = ''
|
||||
${pkgs.procps}/bin/free -m | grep Mem | \
|
||||
${pkgs.gawk}/bin/awk '{print $2 "-" $4}' | \
|
||||
${pkgs.bc}/bin/bc > /home/user/.memory_used
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers."autoballoon" = {
|
||||
description = "Auto update resolution crutch";
|
||||
timerConfig = {
|
||||
OnBootSec = "1s";
|
||||
OnUnitInactiveSec = "1s";
|
||||
Unit = "autoballoon.service";
|
||||
AccuracySec = "1us";
|
||||
};
|
||||
wantedBy = ["timers.target"];
|
||||
};
|
||||
}
|
||||
`)
|
59
builtin.go
Normal file
59
builtin.go
Normal file
@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Builtin VMs
|
||||
|
||||
type app struct {
|
||||
Name string
|
||||
Nix []byte
|
||||
}
|
||||
|
||||
var builtin_chromium_nix = app{
|
||||
Name: "chromium",
|
||||
Nix: []byte(`
|
||||
{pkgs, ...}:
|
||||
let
|
||||
application = "${pkgs.chromium}/bin/chromium";
|
||||
appRunner = pkgs.writeShellScriptBin "app" ''
|
||||
ARGS_FILE=/home/user/.args
|
||||
ARGS=$(cat $ARGS_FILE)
|
||||
rm $ARGS_FILE
|
||||
|
||||
${application} $ARGS
|
||||
systemctl poweroff
|
||||
'';
|
||||
in {
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
programs.chromium = {
|
||||
enable = true;
|
||||
extensions = [
|
||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm" # uBlock Origin
|
||||
"gcbommkclmclpchllfjekcdonpmejbdp" # HTTPS Everywhere
|
||||
"fihnjjcciajhdojfnbdddfaoknhalnja" # I don't care about cookies
|
||||
];
|
||||
};
|
||||
|
||||
services.xserver.displayManager.sessionCommands = "${appRunner}/bin/app &";
|
||||
}
|
||||
`),
|
||||
}
|
||||
|
||||
func writeBuiltinApps(path string) (err error) {
|
||||
for _, f := range []app{
|
||||
builtin_chromium_nix,
|
||||
} {
|
||||
err = ioutil.WriteFile(configDir+"/nix/"+f.Name+".nix", f.Nix, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
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 "}"
|
194
generate.go
Normal file
194
generate.go
Normal file
@ -0,0 +1,194 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/digitalocean/go-libvirt"
|
||||
)
|
||||
|
||||
var template = `
|
||||
{pkgs, ...}:
|
||||
let
|
||||
application = "${pkgs.%s}/bin/%s";
|
||||
appRunner = pkgs.writeShellScriptBin "app" ''
|
||||
ARGS_FILE=/home/user/.args
|
||||
ARGS=$(cat $ARGS_FILE)
|
||||
rm $ARGS_FILE
|
||||
|
||||
${application} $ARGS
|
||||
systemctl poweroff
|
||||
'';
|
||||
in {
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
services.xserver.displayManager.sessionCommands = "${appRunner}/bin/app &";
|
||||
}
|
||||
`
|
||||
|
||||
func isPackageExists(channel, name string) bool {
|
||||
return nil == exec.Command("nix-build", "<"+channel+">", "-A", name).Run()
|
||||
}
|
||||
|
||||
func nixPath(name string) (path string, err error) {
|
||||
command := exec.Command("nix", "path-info", name)
|
||||
bytes, err := command.Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
path = string(bytes)
|
||||
return
|
||||
}
|
||||
|
||||
func guessChannel() (channel string, err error) {
|
||||
command := exec.Command("nix-channel", "--list")
|
||||
bytes, err := command.Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
channels := strings.Split(string(bytes), "\n")
|
||||
for _, line := range channels {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 2 {
|
||||
if strings.Contains(fields[1], "nixos.org/channels") {
|
||||
channel = fields[0]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.New("No channel found")
|
||||
return
|
||||
}
|
||||
|
||||
func filterDotfiles(files []os.FileInfo) (notHiddenFiles []os.FileInfo) {
|
||||
for _, f := range files {
|
||||
if !strings.HasPrefix(f.Name(), ".") {
|
||||
notHiddenFiles = append(notHiddenFiles, f)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func generate(l *libvirt.Libvirt, pkg, bin, vmname string) (err error) {
|
||||
// TODO refactor
|
||||
var name, channel string
|
||||
|
||||
if strings.Contains(pkg, ".") {
|
||||
channel = strings.Split(pkg, ".")[0]
|
||||
name = strings.Join(strings.Split(pkg, ".")[1:], ".")
|
||||
} else {
|
||||
log.Println("Package name does not contains channel")
|
||||
log.Println("Trying to guess")
|
||||
|
||||
channel, err = guessChannel()
|
||||
if err != nil {
|
||||
log.Println("Cannot guess channel")
|
||||
log.Println("Check nix-channel --list")
|
||||
log.Println("Will try <nixpkgs>")
|
||||
channel = "nixpkgs"
|
||||
err = nil
|
||||
}
|
||||
|
||||
name = pkg
|
||||
log.Println("Use", channel+"."+pkg)
|
||||
}
|
||||
|
||||
if !isPackageExists(channel, name) {
|
||||
s := "Package " + name + " does not exists"
|
||||
err = errors.New(s)
|
||||
log.Println(s)
|
||||
return
|
||||
}
|
||||
|
||||
path, err := nixPath(channel + "." + name)
|
||||
if err != nil {
|
||||
log.Println("Cannot find nix path")
|
||||
return
|
||||
}
|
||||
|
||||
path = strings.TrimSpace(path)
|
||||
|
||||
files, err := ioutil.ReadDir(path + "/bin/")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if bin == "" && len(files) != 1 {
|
||||
fmt.Println("There's more than one binary in */bin")
|
||||
fmt.Println("Files in", path+"/bin/:")
|
||||
for _, f := range files {
|
||||
fmt.Println("\t", f.Name())
|
||||
}
|
||||
|
||||
log.Println("Trying to guess binary")
|
||||
var found bool = false
|
||||
|
||||
notHiddenFiles := filterDotfiles(files)
|
||||
if len(notHiddenFiles) == 1 {
|
||||
log.Println("Use", notHiddenFiles[0].Name())
|
||||
bin = notHiddenFiles[0].Name()
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
for _, f := range files {
|
||||
parts := strings.Split(pkg, ".")
|
||||
if f.Name() == parts[len(parts)-1] {
|
||||
log.Println("Use", f.Name())
|
||||
bin = f.Name()
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Println("Cannot guess in */bin, " +
|
||||
"you should specify one of them explicitly")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if bin != "" {
|
||||
var found bool = false
|
||||
for _, f := range files {
|
||||
if bin == f.Name() {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Println("There's no such file in */bin")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
bin = files[0].Name()
|
||||
}
|
||||
|
||||
var appFilename string
|
||||
if vmname != "" {
|
||||
appFilename = configDir + "/nix/" + vmname + ".nix"
|
||||
} else {
|
||||
appFilename = configDir + "/nix/" + name + ".nix"
|
||||
}
|
||||
|
||||
appNixConfig := fmt.Sprintf(template, name, bin)
|
||||
|
||||
err = ioutil.WriteFile(appFilename, []byte(appNixConfig), 0600)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(appNixConfig + "\n")
|
||||
log.Println("Configuration file is saved to", appFilename)
|
||||
return
|
||||
}
|
13
go.mod
Normal file
13
go.mod
Normal file
@ -0,0 +1,13 @@
|
||||
module code.dumpstack.io/tools/appvm
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20190715144809-7b622097a793
|
||||
github.com/go-cmd/cmd v1.1.0
|
||||
github.com/jollheef/go-system v0.0.0-20160710075518-6ed6b1d2b8db
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20190715144809-7b622097a793 h1:+ItaX1GKKT70bYwazNtWeYz8QBfirNC85J70psPGgN0=
|
||||
github.com/digitalocean/go-libvirt v0.0.0-20190715144809-7b622097a793/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw=
|
||||
github.com/go-cmd/cmd v1.1.0 h1:LxXflJCRKNZgoKl/0TJdzIDSGFdik3zxaeyL1yXCTsI=
|
||||
github.com/go-cmd/cmd v1.1.0/go.mod h1:bkfdaV0aMvVwTINGdkU5jlQEd9gF0z4irQutl37pOd8=
|
||||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/jollheef/go-system v0.0.0-20160710075518-6ed6b1d2b8db h1:HGcWru24Gt24VFEsX7mxKtO+/NnKCuQ0LYtardulWMc=
|
||||
github.com/jollheef/go-system v0.0.0-20160710075518-6ed6b1d2b8db/go.mod h1:Cj2JA+Wov6pwK3QTq2PuRXkZ5UM+DT3apJtBDUS8zKE=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -1,4 +1,8 @@
|
||||
package main
|
||||
|
||||
var local_nix_template = []byte(`
|
||||
{
|
||||
services.xserver.layout = "us,ru";
|
||||
services.xserver.xkbOptions = "ctrl:nocaps,grp:rctrl_toggle";
|
||||
}
|
||||
`)
|
@ -1,21 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.etc."chromium/policies/managed/plugins.json".text = ''
|
||||
{
|
||||
"ExtensionInstallForcelist": [
|
||||
// uBlock Origin (https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm)
|
||||
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
|
||||
// HTTPS Everywhere (https://chrome.google.com/webstore/detail/https-everywhere/gcbommkclmclpchllfjekcdonpmejbdp)
|
||||
"gcbommkclmclpchllfjekcdonpmejbdp;https://clients2.google.com/service/update2/crx",
|
||||
]
|
||||
}
|
||||
'';
|
||||
|
||||
environment.systemPackages = [ pkgs.chromium ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.chromium}/bin/chromium; done &";
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.evince ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.evince}/bin/evince; done &";
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.libreoffice ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.libreoffice}/bin/soffice; done &";
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
services.xserver.monitorSection = ''
|
||||
Modeline "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync
|
||||
Option "PreferredMode" "1920x1080_60.00"
|
||||
'';
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.tdesktop ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.tdesktop}/bin/telegram-desktop; done &";
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.thunderbird ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.thunderbird}/bin/thunderbird; done &";
|
||||
}
|
10
nix/wire.nix
10
nix/wire.nix
@ -1,10 +0,0 @@
|
||||
{pkgs, ...}:
|
||||
{
|
||||
imports = [
|
||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
<nix/base.nix>
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.wire-desktop ];
|
||||
services.xserver.displayManager.sessionCommands = "while [ 1 ]; do ${pkgs.wire-desktop}/bin/wire-desktop; done &";
|
||||
}
|
4
shell.nix
Normal file
4
shell.nix
Normal file
@ -0,0 +1,4 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
with pkgs; mkShell {
|
||||
buildInputs = [ go gocode virt-viewer virtmanager ];
|
||||
}
|
93
xml.go
Normal file
93
xml.go
Normal file
@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// You may think that you want to rewrite to proper golang structures.
|
||||
// Believe me, you shouldn't.
|
||||
|
||||
func generateXML(vmName string, online bool,
|
||||
vmNixPath, reginfo, img, sharedDir string) string {
|
||||
|
||||
qemuParams := `
|
||||
<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>
|
||||
`
|
||||
|
||||
if !online {
|
||||
qemuParams = `
|
||||
<qemu:commandline>
|
||||
<qemu:arg value='-snapshot'/>
|
||||
</qemu:commandline>
|
||||
`
|
||||
}
|
||||
|
||||
return fmt.Sprintf(xmlTmpl, vmName, vmNixPath, vmNixPath, vmNixPath,
|
||||
reginfo, img, sharedDir, sharedDir, sharedDir, qemuParams)
|
||||
}
|
||||
|
||||
var xmlTmpl = `
|
||||
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
|
||||
<name>%s</name>
|
||||
<memory unit='GiB'>2</memory>
|
||||
<currentMemory unit='GiB'>1</currentMemory>
|
||||
<vcpu>4</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></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>
|
||||
<!-- Guest additionals support -->
|
||||
<channel type='spicevmc'>
|
||||
<target type='virtio' name='com.redhat.spice.0'/>
|
||||
</channel>
|
||||
<!-- 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>
|
||||
%s
|
||||
</domain>
|
||||
`
|
Reference in New Issue
Block a user