initial commit

Signed-off-by: Christine Dodrill <me@christine.website>
This commit is contained in:
Cadey Ratio 2021-05-16 03:46:55 +00:00
commit a735e4f67e
12 changed files with 687 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
eval "$(lorri direnv)"

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2021 Christine Dodrill <me@christine.website>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

16
data/README-names.md Normal file
View File

@ -0,0 +1,16 @@
# How to generate names.json
Open https://xenoblade.github.io/xb2/bdat/common/BLD_NameList.html and paste
this into the browser inspector:
```js
let names = [];
Array.from(document.getElementsByClassName("sortable")[0].children[1].children)
.forEach(row => names.push(row.children[2]
.innerHTML
.toLowerCase()
.replaceAll(" ", "-")));
console.log(JSON.stringify(names));
```
Then format it with jq.

131
data/distros.dhall Normal file
View File

@ -0,0 +1,131 @@
let Distro =
{ Type =
{ name : Text
, downloadURL : Text
, sha256Sum : Text
, minSize : Natural
}
, default = { name = "", downloadURL = "", sha256Sum = "", minSize = 5 }
}
in [ Distro::{
, name = "alpine-edge"
, downloadURL =
"https://xena.greedo.xeserv.us/pkg/alpine/img/alpine-edge-2021-05-15-cloud-init-within.qcow2"
, sha256Sum =
"c0ed716b9bd3dd45959496af4177935b0c491153c41d5d5e33eaf132bcc130c6"
, minSize = 10
}
, Distro::{
, name = "amazon-linux"
, downloadURL =
"https://cdn.amazonlinux.com/os-images/2.0.20210427.0/kvm/amzn2-kvm-2.0.20210427.0-x86_64.xfs.gpt.qcow2"
, sha256Sum =
"6ef9daef32cec69b2d0088626ec96410cd24afc504d57278bbf2f2ba2b7e529b"
, minSize = 25
}
, Distro::{
, name = "arch"
, downloadURL =
"https://mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg-20210515.22945.qcow2"
, sha256Sum =
"e4077f5ba3c5d545478f64834bc4852f9f7a2e05950fce8ecd0df84193162a27"
, minSize = 2
}
, Distro::{
, name = "centos-7"
, downloadURL =
"https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2"
, sha256Sum =
"1db30c9c272fb37b00111b93dcebff16c278384755bdbe158559e9c240b73b80"
, minSize = 8
}
, Distro::{
, name = "centos-8"
, downloadURL =
"https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.3.2011-20201204.2.x86_64.qcow2"
, sha256Sum =
"7ec97062618dc0a7ebf211864abf63629da1f325578868579ee70c495bed3ba0"
, minSize = 10
}
, Distro::{
, name = "debian-9"
, downloadURL =
"https://cdimage.debian.org/cdimage/openstack/9.13.21-20210511/debian-9.13.21-20210511-openstack-amd64.qcow2"
, sha256Sum =
"0667a08e2d947b331aee068db4bbf3a703e03edaf5afa52e23d534adff44b62a"
, minSize = 2
}
, Distro::{
, name = "debian-10"
, downloadURL =
"https://cdimage.debian.org/images/cloud/buster/20210329-591/debian-10-generic-amd64-20210329-591.qcow2"
, sha256Sum =
"70c61956095870c4082103d1a7a1cb5925293f8405fc6cb348588ec97e8611b0"
, minSize = 2
}
, Distro::{
, name = "fedora-34"
, downloadURL =
"https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2"
, sha256Sum =
"b9b621b26725ba95442d9a56cbaa054784e0779a9522ec6eafff07c6e6f717ea"
, minSize = 5
}
, Distro::{
, name = "opensuse-leap-15.1"
, downloadURL =
"https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.2/images/openSUSE-Leap-15.2-OpenStack.x86_64.qcow2"
, sha256Sum =
"3203e256dab5981ca3301408574b63bc522a69972fbe9850b65b54ff44a96e0a"
, minSize = 10
}
, Distro::{
, name = "opensuse-leap-15.2"
, downloadURL =
"https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.2/images/openSUSE-Leap-15.2.x86_64-NoCloud.qcow2"
, sha256Sum =
"bd3c251ca52f9cf2ee0820258d75fd6d71502447eb0c7ae2592dc9a83ad7a0a1"
, minSize = 10
}
, Distro::{
, name = "ubuntu-16.04"
, downloadURL =
"https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img"
, sha256Sum =
"50a21bc067c05e0c73bf5d8727ab61152340d93073b3dc32eff18b626f7d813b"
, minSize = 5
}
, Distro::{
, name = "ubuntu-18.04"
, downloadURL =
"https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img"
, sha256Sum =
"bea55c09dde0d5c2dbac8a73c2ce4b93061264ba9c354d67939ae0e259d32906"
, minSize = 5
}
, Distro::{
, name = "ubuntu-20.04"
, downloadURL =
"https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
, sha256Sum =
"55e1feee6cbc5fed33505f04dbc2d06124ea06998599e5d3f7a2609b54b439c5"
, minSize = 5
}
, Distro::{
, name = "ubuntu-20.10"
, downloadURL =
"https://cloud-images.ubuntu.com/groovy/current/groovy-server-cloudimg-amd64.img"
, sha256Sum =
"c1332c24557389a129ff98fa169e34cb53c02555ed702a235e26b8978dd004c3"
, minSize = 5
}
, Distro::{
, name = "ubuntu-21.04"
, downloadURL =
"https://cloud-images.ubuntu.com/hirsute/current/hirsute-server-cloudimg-amd64.img"
, sha256Sum =
"2f8a562637340a026f712594f1257673543d74725d8e3daf88d533d7b8bf448f"
, minSize = 5
}
]

1
data/names.json Normal file
View File

@ -0,0 +1 @@
["aizen","akatsuki","akebono","azai","arai","arufumi","ikazuchi","ikaruga","izayoi","izumo","ichiro","ikaku","ikki","issen","inazuma","ushio","oryuu","owashi","okina","oboro","orochi","kaiden","kaibyaku","karkan","kagemitsu","kagero","kazan","katsumasa","kanesada","kanehira","kanemitsu","kei","kijin","kibitsu","gokuto","kirim","gingar","kur","kuzan","kusanagi","kurochi","krogane","genno","kouki","kouru","kogarashi","kokras","gogyo","kojiro","kosor","kotetsu","kongir","konjiki","sakon","sangar","shun","shikiso","shishi","shichisei","shiko","shippun","shiden","shura","shungen","shin-mei","shinra","jin-rai","jinryuu","suro","sulgar","seigai","sysor","seiten","seimei","zeku","zeno","sordai","soten","sohmei","shouryu","sohaya","taiga","daiko","tyzan","taisei","tyhei","tadar","tsurugi","tenka","tenku","denko","toshi","tokka","hagan","hakusui","hakuto","hayate","hayabusa","hanni","hei","hiken","bizen","hynk","hideh","hiden","bakuya","huga","hiryu","fuwei","fugetsu","hukut","fudor","hekireki","bengara","horoh","hokuto","shigan","mikazuchi","mizuchi","mitsukage","mior","mu","mugen","musashi","mujo","musou","muchika","murakumo","murasame","meikyo","mejiro","yago","yakumo","yasha","yata","yanagi","yamato","yuki","yuzen","yuso","yumo","yoshikiri","rykiri","ranmaru","rikuzen","ryusei","ryo","rogen","reiki","roho","ai","aui","auba","akana","yoiyami","asagi","asai","aska","azuki","atori","amanei","amayori","ayame","anzu","koyuki","kyoka","isuzu","ichiku","iroha","uzura","uzuki","umi","ema","orka","kaeda","sarasa","kanami","kanon","karin","karei","karyn","kanna","kiko","kisaragi","kiri","kinsei","quina","kuko","kurumi","kurenai","kogoku","kokutan","kokuyo","kokoro","konoha","kohana","kohaku","sakuya","sazami","satsuki","sango","shiori","shigura","shisui","shizuku","shinome","shinobu","shimoki","shussu","shuraya","shiranui","shirayuki","shirayuri","shirome","suiren","suzu","suzukaze","suzuna","suzuran","sumira","tsumugi","sekirei","setsuka","sora","tamayori","chigusa","chidori","tsugumi","tsukumi","zutsuji","tsubaki","tsumi","tsura","tsuruba","tomae","torwa","nazuna","natsuki","nadeshiko","natori","ne-ne","nenoh","neyuki","nosuri","hasu","hazuki","hatsuharu","hatsuyuki","haruka","harusa","haruna","higana","hisui","hinagi","hinagetsu","hinata","hibari","hibiki","himawari","faera","fubuki","fuyoshi","yuzu","botania","honoka","madoka","mikagami","mikazuki","mika","mikoto","misaki","midori","minazuki","minami","minori","miyuki","mirei","mutsuki","maegi","mochi","momiji","moyoi","yayoi","yuka","yutsuji","yuna","yukina","yukine","yuzuki","yura","yuri","yomogi","rania","rinnia","lindora","lin","ruri","reika","rengenne","wakaba","utsuwaka","kai","kukir","kuro","goemon","koske","kotar","goro","kontro","sasuke","shimaru","shiro","tamar","tamon","tibbi","chamaru","tokoto","totetsu","baku","hutar","pochi","bonten","ryta","riku"]

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/Xe/mkvm
go 1.16
require (
github.com/digitalocean/go-libvirt v0.0.0-20210513152157-a898af65b5cc
github.com/google/uuid v1.2.0
github.com/philandstuff/dhall-golang/v5 v5.0.0
golang.org/x/tools v0.1.1
)

76
go.sum Normal file
View File

@ -0,0 +1,76 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/go-libvirt v0.0.0-20210513152157-a898af65b5cc h1:3KZmBt2nf5+YmSECo+RBnUzP7uCenf/Ckbs5wXLhHXY=
github.com/digitalocean/go-libvirt v0.0.0-20210513152157-a898af65b5cc/go.mod h1:o129ljs6alsIQTc8d6eweihqpmmrbxZ2g1jhgjhPykI=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f h1:lvGFo/tDOSQ4FKu0d2694s8XyOfAL6FLR9DCD5BIUW4=
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU0jivOVFGtOzA6U5AWFs8HE4DRzWLOUE=
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/philandstuff/dhall-golang/v5 v5.0.0 h1:v2k3t7shNw2lzvLTaJ7BN181SD/KsvqmJOQ9L//iBR4=
github.com/philandstuff/dhall-golang/v5 v5.0.0/go.mod h1:rQY4dIWweuKdnU1VQ7jmpiF6uMd/FYhvfz883gDD53w=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

301
main.go Normal file
View File

@ -0,0 +1,301 @@
package main
import (
"bufio"
"bytes"
crand "crypto/rand"
"embed"
"encoding/json"
"flag"
"fmt"
"html/template"
"io"
"log"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/digitalocean/go-libvirt"
"github.com/google/uuid"
"github.com/philandstuff/dhall-golang/v5"
"golang.org/x/tools/txtar"
)
//go:embed data/* templates/*
var data embed.FS
var (
distro = flag.String("distro", "alpine-edge", "the linux distro to install in the VM")
name = flag.String("name", "", "the name of the VM, defaults to a random common blade name")
zvolPrefix = flag.String("zvol-prefix", "rpool/mkvm-test/", "the prefix to use for zvol names")
zvolSize = flag.Int("zvol-size", 0, "the number of gigabytes for the virtual machine disk")
memory = flag.Int("memory", 512, "the number of megabytes of ram for the virtual machine")
)
func main() {
rand.Seed(time.Now().Unix())
flag.Parse()
if *name == "" {
commonBladeName, err := getName()
if err != nil {
log.Fatal(err)
}
name = &commonBladeName
}
distros, err := getDistros()
if err != nil {
log.Fatalf("can't load internal list of distros: %v", err)
}
var resultDistro Distro
var found bool
for _, d := range distros {
if d.Name == *distro {
found = true
resultDistro = d
if *zvolSize == 0 {
zvolSize = &d.MinSize
}
if *zvolSize < d.MinSize {
zvolSize = &d.MinSize
}
}
}
if !found {
fmt.Printf("can't find distro %s in my list. Here are distros I know about:\n", *distro)
for _, d := range distros {
fmt.Println(d.Name)
}
os.Exit(1)
}
zvol := filepath.Join(*zvolPrefix, *name)
macAddress, err := randomMac()
if err != nil {
log.Fatalf("can't generate mac address: %v", err)
}
l, err := connectToLibvirt()
if err != nil {
log.Fatalf("can't connect to libvirt: %v", err)
}
log.Println("plan:")
log.Printf("name: %s", *name)
log.Printf("zvol: %s (%d GB)", zvol, *zvolSize)
log.Printf("base image url: %s", resultDistro.DownloadURL)
log.Printf("mac address: %s", macAddress)
log.Printf("ram: %d MB", *memory)
reader := bufio.NewReader(os.Stdin)
fmt.Print("press enter if this looks okay:")
reader.ReadString('\n')
cdir, err := os.UserCacheDir()
if err != nil {
log.Fatalf("can't find cache dir: %v", err)
}
cdir = filepath.Join(cdir, "within", "mkvm")
os.MkdirAll(filepath.Join(cdir, "qcow2"), 0755)
os.MkdirAll(filepath.Join(cdir, "seed"), 0755)
qcowPath := filepath.Join(cdir, "qcow2", resultDistro.Sha256Sum)
_, err = os.Stat(qcowPath)
if err != nil {
log.Printf("downloading distro image %s to %s", resultDistro.DownloadURL, qcowPath)
fout, err := os.Create(qcowPath)
if err != nil {
log.Fatal(err)
}
resp, err := http.Get(resultDistro.DownloadURL)
if err != nil {
log.Fatalf("can't fetch qcow2 for %s (%s): %v", resultDistro.Name, resultDistro.DownloadURL, err)
}
if resp.StatusCode != http.StatusOK {
log.Fatalf("%s replied %s", resultDistro.DownloadURL, resp.Status)
}
_, err = io.Copy(fout, resp.Body)
if err != nil {
log.Fatalf("download of %s failed: %v", resultDistro.DownloadURL, err)
}
fout.Close()
resp.Body.Close()
}
tmpl := template.Must(template.ParseFS(data, "templates/*"))
var buf = bytes.NewBuffer(nil)
err = tmpl.ExecuteTemplate(buf, "cloud-config.txtar", struct{ Name string }{Name: *name})
if err != nil {
log.Fatalf("can't generate cloud-config: %v", err)
}
arc := txtar.Parse(buf.Bytes())
dir, err := os.MkdirTemp("", "mkvm")
if err != nil {
log.Fatalf("can't make directory: %v", err)
}
for _, file := range arc.Files {
fout, err := os.Create(filepath.Join(dir, file.Name))
if err != nil {
log.Fatal(err)
}
_, err = fout.Write(file.Data)
if err != nil {
log.Fatal(err)
}
}
isoPath := filepath.Join(cdir, "seed", fmt.Sprintf("%s-%s.iso", *name, resultDistro.Name))
err = run(
"genisoimage",
"-output",
isoPath,
"-volid",
"cidata",
"-joliet",
"-rock",
filepath.Join(dir, "meta-data"),
filepath.Join(dir, "user-data"),
)
if err != nil {
log.Fatal(err)
}
ram := *memory * 1024
vmID := uuid.New().String()
buf.Reset()
// zfs create -V 20G rpool/safe/vm/sena
err = run("sudo", "zfs", "create", "-V", fmt.Sprintf("%dG", *zvolSize), zvol)
if err != nil {
log.Fatalf("can't create zvol %s: %v", zvol, err)
}
err = run("sudo", "qemu-img", "convert", "-O", "raw", qcowPath, filepath.Join("/dev/zvol", zvol))
if err != nil {
log.Fatalf("can't import qcow2: %v", err)
}
err = tmpl.ExecuteTemplate(buf, "base.xml", struct {
Name string
UUID string
Memory int
ZVol string
Seed string
MACAddress string
}{
Name: *name,
UUID: vmID,
Memory: ram,
ZVol: zvol,
Seed: isoPath,
MACAddress: macAddress,
})
if err != nil {
log.Fatalf("can't generate VM template: %v", err)
}
domain, err := mkVM(l, buf)
if err != nil {
log.Printf("can't create domain for %s: %v", *name, err)
log.Println("you should run this command:")
log.Println()
log.Printf("zfs destroy %s", zvol)
os.Exit(1)
}
log.Printf("created %s", domain.Name)
}
func randomMac() (string, error) {
buf := make([]byte, 6)
_, err := crand.Read(buf)
if err != nil {
return "", err
}
buf[0] = (buf[0] | 2) & 0xfe
return net.HardwareAddr(buf).String(), nil
}
func getName() (string, error) {
var names []string
nameData, err := data.ReadFile("data/names.json")
if err != nil {
return "", err
}
err = json.Unmarshal(nameData, &names)
if err != nil {
return "", err
}
return names[rand.Intn(len(names))], nil
}
func run(args ...string) error {
log.Println("running command:", strings.Join(args, " "))
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func connectToLibvirt() (*libvirt.Libvirt, error) {
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
if err != nil {
return nil, fmt.Errorf("can't dial libvirt: %w", err)
}
l := libvirt.New(c)
_, err = l.AuthPolkit()
if err != nil {
return nil, fmt.Errorf("can't auth with polkit: %w", err)
}
if err := l.Connect(); err != nil {
return nil, fmt.Errorf("can't connect: %w", err)
}
return l, nil
}
func mkVM(l *libvirt.Libvirt, buf *bytes.Buffer) (*libvirt.Domain, error) {
domain, err := l.DomainCreateXML(buf.String(), libvirt.DomainNone)
return &domain, err
}
type Distro struct {
Name string `dhall:"name" json:"name"`
DownloadURL string `dhall:"downloadURL" json:"download_url"`
Sha256Sum string `dhall:"sha256Sum" json:"sha256_sum"`
MinSize int `dhall:"minSize" json:"min_size"`
}
func getDistros() ([]Distro, error) {
distroData, err := data.ReadFile("data/distros.dhall")
if err != nil {
return nil, err
}
var result []Distro
err = dhall.Unmarshal(distroData, &result)
if err != nil {
return nil, err
}
return result, nil
}

26
main_test.go Normal file
View File

@ -0,0 +1,26 @@
package main
import "testing"
func TestGetName(t *testing.T) {
name, err := getName()
if err != nil {
t.Fatal(err)
}
t.Log(name)
}
func TestGetDistros(t *testing.T) {
_, err := getDistros()
if err != nil {
t.Fatal(err)
}
}
func TestRandomMac(t *testing.T) {
mac, err := randomMac()
if err != nil {
t.Fatal(err)
}
t.Log(mac)
}

7
shell.nix Normal file
View File

@ -0,0 +1,7 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
dhall go goimports gopls cdrkit
];
}

71
templates/base.xml Normal file
View File

@ -0,0 +1,71 @@
<domain type="kvm">
<name>{{.Name}}</name>
<uuid>{{.UUID}}</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://nixos.org/nixos/unstable"/>
</libosinfo:libosinfo>
</metadata>
<memory>{{.Memory}}</memory>
<currentMemory>{{.Memory}}</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<boot dev="hd"/>
</os>
<features>
<acpi/>
<apic/>
<vmport state="off"/>
</features>
<cpu mode="host-model"/>
<clock offset="utc">
<timer name="rtc" tickpolicy="catchup"/>
<timer name="pit" tickpolicy="delay"/>
<timer name="hpet" present="no"/>
</clock>
<pm>
<suspend-to-mem enabled="no"/>
<suspend-to-disk enabled="no"/>
</pm>
<devices>
<emulator>/run/libvirt/nix-emulators/qemu-system-x86_64</emulator>
<disk type="block" device="disk">
<driver name="qemu" type="raw" cache="none" io="native"/>
<source dev="/dev/zvol/{{.ZVol}}"/>
<target dev="vda" bus="virtio"/>
</disk>
<disk type="file" device="cdrom">
<driver name="qemu" type="raw"/>
<source file="{{.Seed}}"/>
<target dev="sda" bus="sata"/>
<readonly/>
</disk>
<controller type="usb" model="qemu-xhci" ports="15"/>
<interface type="network">
<source network="default"/>
<mac address="{{.MACAddress}}"/>
<model type="virtio"/>
</interface>
<console type="pty"/>
<channel type="unix">
<source mode="bind"/>
<target type="virtio" name="org.qemu.guest_agent.0"/>
</channel>
<channel type="spicevmc">
<target type="virtio" name="com.redhat.spice.0"/>
</channel>
<input type="tablet" bus="usb"/>
<graphics type="spice" port="-1" tlsPort="-1" autoport="yes"/>
<sound model="ich9"/>
<video>
<model type="qxl"/>
</video>
<redirdev bus="usb" type="spicevmc"/>
<redirdev bus="usb" type="spicevmc"/>
<memballoon model="virtio"/>
<rng model="virtio">
<backend model="random">/dev/urandom</backend>
</rng>
</devices>
</domain>

View File

@ -0,0 +1,28 @@
-- meta-data --
local-hostname: {{.Name}}
-- user-data --
#cloud-config
#vim:syntax=yaml
cloud_config_modules:
- runcmd
cloud_final_modules:
- [users-groups, always]
- [scripts-user, once-per-instance]
users:
- name: xe
plain_text_passwd: hunter2
groups: [ wheel ]
sudo: [ "ALL=(ALL) NOPASSWD:ALL" ]
shell: /bin/bash
ssh-authorized-keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPg9gYKVglnO2HQodSJt4z4mNrUSUiyJQ7b+J798bwD9 cadey@shachi
write_files:
- path: /etc/cloud/cloud.cfg.d/80_disable_network_after_firstboot.cfg
content: |
# Disable network configuration after first boot
network:
config: disabled