commit a735e4f67ec063531e86d7b297eeed2cb0143b2d Author: Christine Dodrill Date: Sun May 16 03:46:55 2021 +0000 initial commit Signed-off-by: Christine Dodrill diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..051d09d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..010878a --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Christine Dodrill + +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. \ No newline at end of file diff --git a/data/README-names.md b/data/README-names.md new file mode 100644 index 0000000..dc7ee65 --- /dev/null +++ b/data/README-names.md @@ -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. diff --git a/data/distros.dhall b/data/distros.dhall new file mode 100644 index 0000000..0e60982 --- /dev/null +++ b/data/distros.dhall @@ -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 + } + ] diff --git a/data/names.json b/data/names.json new file mode 100644 index 0000000..0cadbd0 --- /dev/null +++ b/data/names.json @@ -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"] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7388453 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f016b95 --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..dac2b4a --- /dev/null +++ b/main.go @@ -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 +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..763bf3c --- /dev/null +++ b/main_test.go @@ -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) +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..b43306e --- /dev/null +++ b/shell.nix @@ -0,0 +1,7 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + dhall go goimports gopls cdrkit + ]; +} diff --git a/templates/base.xml b/templates/base.xml new file mode 100644 index 0000000..f3cefb9 --- /dev/null +++ b/templates/base.xml @@ -0,0 +1,71 @@ + + {{.Name}} + {{.UUID}} + + + + + + {{.Memory}} + {{.Memory}} + 2 + + hvm + + + + + + + + + + + + + + + + + + + /run/libvirt/nix-emulators/qemu-system-x86_64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /dev/urandom + + + diff --git a/templates/cloud-config.txtar b/templates/cloud-config.txtar new file mode 100644 index 0000000..6ef444c --- /dev/null +++ b/templates/cloud-config.txtar @@ -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