-
Notifications
You must be signed in to change notification settings - Fork 157
/
kvm-install-vm
executable file
·1123 lines (1014 loc) · 35.3 KB
/
kvm-install-vm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
set -e
# Set program name variable - basename without subshell
prog=${0##*/}
function usage ()
{
cat << EOF
NAME
kvm-install-vm - Install virtual guests using cloud-init on a local KVM
hypervisor.
SYNOPSIS
$prog COMMAND [OPTIONS]
DESCRIPTION
A bash wrapper around virt-install to build virtual machines on a local KVM
hypervisor. You can run it as a normal user which will use qemu:///session
to connect locally to your KVM domains.
COMMANDS
help - show this help or help for a subcommand
attach-disk - create and attach a disk device to guest domain
create - create a new guest domain
detach-disk - detach a disk device from a guest domain
list - list all domains, running and stopped
remove - delete a guest domain
EOF
exit 0
}
function usage_subcommand ()
{
case "$1" in
create)
printf "NAME\n"
printf " $prog create [COMMANDS] [OPTIONS] VMNAME\n"
printf "\n"
printf "DESCRIPTION\n"
printf " Create a new guest domain.\n"
printf "\n"
printf "COMMANDS\n"
printf " help - show this help\n"
printf "\n"
printf "OPTIONS\n"
printf " -a Autostart (default: false)\n"
printf " -b Bridge (default: virbr0)\n"
printf " -c Number of vCPUs (default: 1)\n"
printf " -d Disk Size (GB) (default: 10)\n"
printf " -D DNS Domain (default: example.local)\n"
printf " -f CPU Model / Feature (default: host)\n"
printf " -g Graphics type (default: spice)\n"
printf " -h Display help\n"
printf " -i Custom QCOW2 Image\n"
printf " -k SSH Public Key (default: $HOME/.ssh/id_rsa.pub)\n"
printf " -l Location of Images (default: $HOME/virt/images)\n"
printf " -L Location of VMs (default: $HOME/virt/vms)\n"
printf " -m Memory Size (MB) (default: 1024)\n"
printf " -M Mac address (default: auto-assigned)\n"
printf " -p Console port (default: auto)\n"
printf " -s Custom shell script\n"
printf " -t Linux Distribution (default: centos8)\n"
printf " -T Timezone (default: US/Eastern)\n"
printf " -u Custom user (default: $USER)\n"
printf " -y Assume yes to prompts (default: false)\n"
printf " -n Assume no to prompts (default: false)\n"
printf " -v Be verbose\n"
printf "\n"
printf "DISTRIBUTIONS\n"
printf " NAME DESCRIPTION LOGIN\n"
printf " amazon2 Amazon Linux 2 ec2-user\n"
printf " centos8 CentOS 8 centos\n"
printf " centos7 CentOS 7 centos\n"
printf " centos7-atomic CentOS 7 Atomic Host centos\n"
printf " centos6 CentOS 6 centos\n"
printf " debian9 Debian 9 (Stretch) debian\n"
printf " debian10 Debian 10 (Buster) debian\n"
printf " fedora29 Fedora 29 fedora\n"
printf " fedora29-atomic Fedora 29 Atomic Host fedora\n"
printf " fedora30 Fedora 30 fedora\n"
printf " fedora31 Fedora 31 fedora\n"
printf " fedora32 Fedora 32 fedora\n"
printf " fedora33 Fedora 33 fedora\n"
printf " fedora34 Fedora 34 fedora\n"
printf " opensuse15 OpenSUSE Leap 15.2 opensuse\n"
printf " ubuntu1604 Ubuntu 16.04 LTS (Xenial Xerus) ubuntu\n"
printf " ubuntu1804 Ubuntu 18.04 LTS (Bionic Beaver) ubuntu\n"
printf " ubuntu2004 Ubuntu 20.04 LTS (Focal Fossa) ubuntu\n"
printf " ubuntu2204 Ubuntu 22.04 LTS (Jammy Jellyfish) ubuntu\n"
printf " rocky8.5 Rocky Linux rocky\n"
printf "\n"
printf "EXAMPLES\n"
printf " $prog create foo\n"
printf " Create VM with the default parameters: CentOS 8, 1 vCPU, 1GB RAM, 10GB\n"
printf " disk capacity.\n"
printf "\n"
printf " $prog create -c 2 -m 2048 -d 20 foo\n"
printf " Create VM with custom parameters: 2 vCPUs, 2GB RAM, and 20GB disk\n"
printf " capacity.\n"
printf "\n"
printf " $prog create -t debian9 foo\n"
printf " Create a Debian 9 VM with the default parameters.\n"
printf "\n"
printf " $prog create -T UTC foo\n"
printf " Create a default VM with UTC timezone.\n"
printf "\n"
;;
remove)
printf "NAME\n"
printf " $prog remove [COMMANDS] VMNAME\n"
printf "\n"
printf "DESCRIPTION\n"
printf " Destroys (stops) and undefines a guest domain. This also remove the\n"
printf " associated storage pool.\n"
printf "\n"
printf "COMMANDS\n"
printf " help - show this help\n"
printf "\n"
printf "OPTIONS\n"
printf " -l Location of Images (default: $HOME/virt/images)\n"
printf " -L Location of VMs (default: $HOME/virt/vms)\n"
printf " -v Be verbose\n"
printf "\n"
printf "EXAMPLE\n"
printf " $prog remove foo\n"
printf " Remove (destroy and undefine) a guest domain. WARNING: This will\n"
printf " delete the guest domain and any changes made inside it!\n"
;;
attach-disk)
printf "NAME\n"
printf " $prog attach-disk [OPTIONS] [COMMANDS] VMNAME\n"
printf "\n"
printf "DESCRIPTION\n"
printf " Attaches a new disk to a guest domain.\n"
printf "\n"
printf "COMMANDS\n"
printf " help - show this help\n"
printf "\n"
printf "OPTIONS\n"
printf " -d SIZE Disk size (GB)\n"
printf " -f FORMAT Disk image format (default: qcow2)\n"
printf " -s IMAGE Source of disk device\n"
printf " -t TARGET Disk device target\n"
printf "\n"
printf "EXAMPLE\n"
printf " $prog attach-disk -d 10 -s example-5g.qcow2 -t vdb foo\n"
printf " Attach a 10GB disk device named example-5g.qcow2 to the foo guest\n"
printf " domain.\n"
;;
list)
printf "NAME\n"
printf " $prog list\n"
printf "\n"
printf "DESCRIPTION\n"
printf " Lists all running and stopped guest domains.\n"
;;
*)
printf "'$subcommand' is not a valid subcommand.\n"
exit 1
;;
esac
exit 0
}
# Console output colors
bold() { echo -e "\e[1m$@\e[0m" ; }
red() { echo -e "\e[31m$@\e[0m" ; }
green() { echo -e "\e[32m$@\e[0m" ; }
yellow() { echo -e "\e[33m$@\e[0m" ; }
die() { red "ERR: $@" >&2 ; exit 2 ; }
silent() { "$@" > /dev/null 2>&1 ; }
output() { echo -e "- $@" ; }
outputn() { echo -en "- $@ ... " ; }
ok() { green "${@:-OK}" ; }
pushd() { command pushd "$@" >/dev/null ; }
popd() { command popd "$@" >/dev/null ; }
# Join zero or more strings into a delimited string.
function join ()
{
local sep="$1"
if [ $# -eq 0 ]; then
return
fi
shift
while [ $# -gt 1 ]; do
printf "%s%s" "$1" "$sep"
shift
done
printf "%s\n" "$1"
}
# Print an optional name=value[,value,..] parameter.
# Prints nothing if no values are given.
function param ()
{
if [ $# -lt 2 ]; then
return # skip empty value
fi
local name="$1"
shift
local values="$(join ',' "$@")"
printf "%s=%s\n" $name $values
}
# Output a command, one argument per line.
function output_command ()
{
local line_cont=$' \\ \n '
local command_lines=$(join "$line_cont" "$@")
printf " %s\n" "$command_lines"
}
# Command wrapper to output the command to be run in verbose
# mode and redirect stdout and stderr to the vm log file.
function run ()
{
local msg="$1"
shift
if [ "${VERBOSE}" -eq 1 ]
then
output "$msg with the following command"
output_command "$@"
else
outputn "$msg"
fi
( "$@" &>> ${VMNAME}.log && ok )
}
# Detect OS and set wget parameters
function set_wget ()
{
if [ -f /etc/fedora-release ]
then
WGET="wget --quiet --show-progress"
else
WGET="wget"
fi
}
function check_vmname_set ()
{
[ -n "${VMNAME}" ] || die "VMNAME not set."
}
function delete_vm ()
{
# Check if domain exists and set DOMAIN_EXISTS variable.
domain_exists "${VMNAME}"
# Check if storage pool exists and set STORPOOL_EXISTS variable.
storpool_exists "${VMNAME}"
check_vmname_set
if [ "${DOMAIN_EXISTS}" -eq 1 ]
then
outputn "Destroying ${VMNAME} domain"
virsh destroy --graceful ${VMNAME} > /dev/null 2>&1 \
&& ok \
|| yellow "(Domain is not running.)"
outputn "Undefining ${VMNAME} domain"
virsh undefine --managed-save --snapshots-metadata ${VMNAME} > /dev/null 2>&1 \
&& ok \
|| die "Could not undefine domain."
else
output "Domain ${VMNAME} does not exist"
fi
[[ -d ${VMDIR}/${VMNAME} ]] && DISKDIR=${VMDIR}/${VMNAME} || DISKDIR=${IMAGEDIR}/${VMNAME}
[ -d $DISKDIR ] \
&& outputn "Deleting ${VMNAME} files" \
&& rm -rf $DISKDIR \
&& ok
if [ "${STORPOOL_EXISTS}" -eq 1 ]
then
outputn "Destroying ${VMNAME} storage pool"
virsh pool-destroy ${VMNAME} > /dev/null 2>&1 && ok
else
output "Storage pool ${VMNAME} does not exist"
fi
}
function fetch_images ()
{
# Create image directory if it doesn't already exist
mkdir -p ${IMAGEDIR}
# Set variables based on $DISTRO
# Use the command "osinfo-query os" to get the list of the accepted OS variants.
case "$DISTRO" in
amazon2)
QCOW=amzn2-kvm-2.0.20190313-x86_64.xfs.gpt.qcow2
OS_TYPE="linux"
OS_VARIANT="auto"
IMAGE_URL=https://cdn.amazonlinux.com/os-images/2.0.20190313/kvm
DISK_FORMAT=qcow2
LOGIN_USER=ec2-user
;;
centos8)
QCOW=CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="centos8"
IMAGE_URL=https://cloud.centos.org/centos/8/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=centos
;;
centos7)
QCOW=CentOS-7-x86_64-GenericCloud.qcow2
OS_TYPE="linux"
OS_VARIANT="centos7.0"
IMAGE_URL=https://cloud.centos.org/centos/7/images
DISK_FORMAT=qcow2
LOGIN_USER=centos
;;
centos7-atomic)
QCOW=CentOS-Atomic-Host-7-GenericCloud.qcow2
OS_TYPE="linux"
OS_VARIANT="centos7.0"
IMAGE_URL=http://cloud.centos.org/centos/7/atomic/images
DISK_FORMAT=qcow2
LOGIN_USER=centos
;;
centos6)
QCOW=CentOS-6-x86_64-GenericCloud.qcow2
OS_TYPE="linux"
OS_VARIANT="centos6.9"
IMAGE_URL=https://cloud.centos.org/centos/6/images
DISK_FORMAT=qcow2
LOGIN_USER=centos
;;
debian8)
# FIXME: Not yet working.
QCOW=debian-8-openstack-amd64.qcow2
OS_TYPE="linux"
OS_VARIANT="debian8"
IMAGE_URL=https://cdimage.debian.org/cdimage/openstack/current-8
DISK_FORMAT=qcow2
LOGIN_USER=debian
;;
debian9)
QCOW=debian-9-openstack-amd64.qcow2
OS_TYPE="linux"
OS_VARIANT="debian9"
IMAGE_URL=https://cdimage.debian.org/cdimage/openstack/current-9
DISK_FORMAT=qcow2
LOGIN_USER=debian
;;
debian10)
QCOW=debian-10-openstack-amd64.qcow2
OS_TYPE="linux"
OS_VARIANT="debian10"
IMAGE_URL=https://cdimage.debian.org/cdimage/openstack/current-10
DISK_FORMAT=qcow2
LOGIN_USER=debian
;;
fedora29)
QCOW=Fedora-Cloud-Base-29-1.2.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora29"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/29/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora29-atomic)
QCOW=Fedora-AtomicHost-29-20190611.0.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora29"
IMAGE_URL=https://download.fedoraproject.org/pub/alt/atomic/stable/Fedora-29-updates-20190611.0/AtomicHost/x86_64/images/
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora30)
QCOW=Fedora-Cloud-Base-30-1.2.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora29"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/30/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora31)
QCOW=Fedora-Cloud-Base-31-1.9.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora31"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora32)
QCOW=Fedora-Cloud-Base-32-1.6.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora32"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/32/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora33)
QCOW=Fedora-Cloud-Base-33-1.2.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora33"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/33/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
fedora34)
QCOW=Fedora-Cloud-Base-34-1.2.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="fedora34"
IMAGE_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images
DISK_FORMAT=qcow2
LOGIN_USER=fedora
;;
ubuntu1604)
QCOW=ubuntu-16.04-server-cloudimg-amd64-disk1.img
OS_TYPE="linux"
OS_VARIANT="ubuntu16.04"
IMAGE_URL=https://cloud-images.ubuntu.com/releases/16.04/release
DISK_FORMAT=qcow2
LOGIN_USER=ubuntu
;;
ubuntu1804)
QCOW=ubuntu-18.04-server-cloudimg-amd64.img
OS_TYPE="linux"
OS_VARIANT="ubuntu18.04"
IMAGE_URL=https://cloud-images.ubuntu.com/releases/18.04/release
DISK_FORMAT=qcow2
LOGIN_USER=ubuntu
;;
ubuntu2004)
QCOW=ubuntu-20.04-server-cloudimg-amd64.img
OS_TYPE="linux"
OS_VARIANT="ubuntu20.04"
IMAGE_URL=https://cloud-images.ubuntu.com/releases/20.04/release
DISK_FORMAT=qcow2
LOGIN_USER=ubuntu
;;
ubuntu2204)
QCOW=ubuntu-22.04-server-cloudimg-amd64.img
OS_TYPE="linux"
OS_VARIANT="ubuntu22.04"
IMAGE_URL=https://cloud-images.ubuntu.com/releases/22.04/release
DISK_FORMAT=qcow2
LOGIN_USER=ubuntu
;;
opensuse15)
QCOW=openSUSE-Leap-15.2-OpenStack.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="opensuse15.0"
IMAGE_URL=https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.2/images
DISK_FORMAT=qcow2
LOGIN_USER=opensuse
;;
rocky85)
QCOW=Rocky-8-GenericCloud-8.5-20211114.2.x86_64.qcow2
OS_TYPE="linux"
OS_VARIANT="rocky8.5"
IMAGE_URL=https://dl.rockylinux.org/pub/rocky/8.5/images
DISK_FORMAT=qcow2
LOGIN_USER=rocky
;;
*)
die "${DISTRO} not a supported OS. Run 'kvm-install-vm create help'."
;;
esac
IMAGE=${IMAGEDIR}/${QCOW}
if [ ! -f ${IMAGEDIR}/${QCOW} ]
then
set_wget
if [ -f ${IMAGEDIR}/${QCOW}.part ]
then
CONTINUE="--continue"
output "Partial cloud image found. Resuming download"
else
CONTINUE=""
output "Cloud image not found. Downloading"
fi
${WGET} \
${CONTINUE} \
--directory-prefix ${IMAGEDIR} \
--output-document=${IMAGEDIR}/${QCOW}.part \
${IMAGE_URL}/${QCOW} || \
die "Could not download image."
mv ${IMAGEDIR}/${QCOW}.part ${IMAGEDIR}/${QCOW}
fi
}
function check_ssh_key ()
{
local key
if [ -z "${PUBKEY}" ]; then
# Try to find a suitable key file.
for key in ~/.ssh/id_{rsa,dsa,ed25519}.pub; do
if [ -f "$key" ]; then
PUBKEY="$key"
break
fi
done
fi
if [ ! -f "${PUBKEY}" ]
then
# Check for existence of a pubkey, or else exit with message
die "Please generate an SSH keypair using 'ssh-keygen -t rsa' or \
specify one with the "-k" flag."
else
# Place contents of $PUBKEY into $KEY
KEY=$(<${PUBKEY})
fi
}
function check_os_variant ()
{
if [[ ${OS_VARIANT} != auto ]]; then
osinfo-query os short-id=${OS_VARIANT} >/dev/null \
|| die "Unknown OS variant '${OS_VARIANT}'. Please update your osinfo-db. "\
"See https://libosinfo.org/download for more information."
fi
}
function domain_exists ()
{
virsh dominfo "${1}" > /dev/null 2>&1 \
&& DOMAIN_EXISTS=1 \
|| DOMAIN_EXISTS=0
}
function storpool_exists ()
{
virsh pool-info "${1}" > /dev/null 2>&1 \
&& STORPOOL_EXISTS=1 \
|| STORPOOL_EXISTS=0
}
function set_sudo_group ()
{
case "${DISTRO}" in
centos*|fedora*|rocky*|*-atomic|amazon*|opensuse* )
SUDOGROUP="wheel"
;;
ubuntu*|debian* )
SUDOGROUP="sudo"
;;
*)
die "OS not supported."
;;
esac
}
function set_cloud_init_remove ()
{
case "${DISTRO}" in
centos6 )
CLOUDINITDISABLE="chkconfig cloud-init off"
;;
centos8|centos7|amazon*|fedora*|rocky*|ubuntu*|debian*|opensuse* )
CLOUDINITDISABLE="systemctl disable cloud-init.service"
;;
*-atomic)
CLOUDINITDISABLE="/usr/bin/true"
;;
esac
}
function set_network_restart_cmd ()
{
case "${DISTRO}" in
centos6 ) NETRESTART="service network stop && service network start" ;;
ubuntu*|debian*) NETRESTART="systemctl stop networking && systemctl start networking" ;;
*) NETRESTART="systemctl stop network && systemctl start network" ;;
esac
}
function check_delete_known_host ()
{
output "Checking for ${IP} in known_hosts file"
grep -q ${IP} ${HOME}/.ssh/known_hosts \
&& outputn "Found entry for ${IP}. Removing" \
&& (sed --in-place "/^${IP}/d" ~/.ssh/known_hosts && ok ) \
|| output "No entries found for ${IP}"
}
function create_vm ()
{
# Create image directory if it doesn't already exist
mkdir -p ${VMDIR}
check_vmname_set
# Start clean
[ -d "${VMDIR}/${VMNAME}" ] && rm -rf ${VMDIR}/${VMNAME}
mkdir -p ${VMDIR}/${VMNAME}
pushd ${VMDIR}/${VMNAME}
# Create log file
touch ${VMNAME}.log
# cloud-init config: set hostname, remove cloud-init package,
# and add ssh-key
cat > $USER_DATA << _EOF_
Content-Type: multipart/mixed; boundary="==BOUNDARY=="
MIME-Version: 1.0
--==BOUNDARY==
Content-Type: text/cloud-config; charset="us-ascii"
#cloud-config
# Hostname management
preserve_hostname: False
hostname: ${VMNAME}
fqdn: ${VMNAME}.${DNSDOMAIN}
# Users
users:
- default
- name: ${ADDITIONAL_USER}
groups: ['${SUDOGROUP}']
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh-authorized-keys:
- ${KEY}
# Configure where output will go
output:
all: ">> /var/log/cloud-init.log"
# configure interaction with ssh server
ssh_genkeytypes: ['ed25519', 'rsa']
# Install my public ssh key to the first user-defined user configured
# in cloud.cfg in the template (which is centos for CentOS cloud images)
ssh_authorized_keys:
- ${KEY}
timezone: ${TIMEZONE}
# Remove cloud-init when finished with it
runcmd:
- ${NETRESTART}
- ${CLOUDINITDISABLE}
_EOF_
if [ ! -z "${SCRIPTNAME+x}" ]
then
SCRIPT=$(< $SCRIPTNAME)
cat >> $USER_DATA << _EOF_
--==BOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"
${SCRIPT}
--==BOUNDARY==--
_EOF_
else
cat >> $USER_DATA << _EOF_
--==BOUNDARY==--
_EOF_
fi
{ echo "instance-id: ${VMNAME}"; echo "local-hostname: ${VMNAME}"; } > $META_DATA
outputn "Copying cloud image ($(basename ${IMAGE}))"
DISK=${VMNAME}.qcow2
qemu-img create -q -f qcow2 -F qcow2 -b $IMAGE $DISK && ok
if $RESIZE_DISK
then
outputn "Resizing the disk to $DISK_SIZE"
# Workaround to prevent virt-resize from renumbering partitions and breaking grub
# See https://bugzilla.redhat.com/show_bug.cgi?id=1472039
# Ubuntu will automatically grow the partition to the new size on its first boot
case "$DISTRO" in
ubuntu*|amazon2)
qemu-img resize $DISK $DISK_SIZE &>> ${VMNAME}.log \
&& ok \
|| die "Could not resize disk."
;;
*)
qemu-img create -f qcow2 \
-o preallocation=metadata $DISK.new $DISK_SIZE &>> ${VMNAME}.log \
&& virt-resize --quiet --expand /dev/sda1 $DISK $DISK.new &>> ${VMNAME}.log \
&& (mv $DISK.new $DISK && ok) \
|| die "Could not resize disk."
;;
esac
fi
# Create CD-ROM ISO with cloud-init config
outputn "Generating ISO for cloud-init"
if command -v genisoimage &>/dev/null
then
genisoimage -output $CI_ISO \
-volid cidata \
-joliet -r $USER_DATA $META_DATA &>> ${VMNAME}.log \
&& ok \
|| die "Could not generate ISO."
else
mkisofs -o $CI_ISO -V cidata -J -r $USER_DATA $META_DATA &>> ${VMNAME}.log \
&& ok \
|| die "Could not generate ISO."
fi
# Create new storage pool for new VM
run "Creating storage pool" \
virsh pool-create-as \
--name=${VMNAME} \
--type=dir \
--target=${VMDIR}/${VMNAME} \
|| die "Could not create storage pool."
# Add custom MAC Address if specified
NETWORK_PARAMS="$(join ',' \
$(param bridge ${BRIDGE}) \
$(param model ${NETWORK_MODEL}) \
$(param mac ${MACADDRESS}) \
${NETWORK_EXTRA})"
# Assemble disk parameters.
DISK_PARAMS="$(join ',' \
${DISK} \
$(param format ${DISK_FORMAT}) \
$(param bus ${DISK_BUS}) \
${DISK_EXTRA})"
# Assemble CI ISO disk parameters.
CI_ISO_PARAMS="$(join ',' \
${CI_ISO} \
${CI_ISO_EXTRA})"
# Omit the --graphics option to auto-detect.
if [ "${GRAPHICS}" = 'auto' ]
then
GRAPHICS_PARAMS=""
else
GRAPHICS_PARAMS="$(join ',' \
${GRAPHICS} \
$(param port ${PORT}) \
$(param listen ${GRAPHICS_LISTEN}) \
${GRAPHICS_EXTRA})"
fi
# Assemble virt-install options.
NETWORK_OPTION="$(param --network ${NETWORK_PARAMS})"
DISK_OPTION="$(param --disk ${DISK_PARAMS})"
CI_ISO_OPTION="$(param --disk ${CI_ISO_PARAMS})"
GRAPHICS_OPTION="$(param --graphics ${GRAPHICS_PARAMS})"
# Call virt-install to import the cloud image and create a new VM
run "Installing the domain" \
virt-install --import \
--name=${VMNAME} \
--memory=${MEMORY} \
--vcpus=${CPUS} \
--cpu=${FEATURE} \
${DISK_OPTION} \
${CI_ISO_OPTION} \
${NETWORK_OPTION} \
--os-type=${OS_TYPE} \
--os-variant=${OS_VARIANT} \
--noautoconsole \
${GRAPHICS_OPTION} \
${VIRT_INSTALL_EXTRA} \
|| die "Could not create domain with virt-install."
virsh dominfo ${VMNAME} &>> ${VMNAME}.log
# Enable autostart if true
if $AUTOSTART
then
outputn "Enabling autostart"
virsh autostart \
--domain ${VMNAME} > /dev/null 2>&1 \
&& ok \
|| die "Could not enable autostart."
fi
# Eject cdrom
virsh detach-disk --domain ${VMNAME} ${VMDIR}/${VMNAME}/${CI_ISO} --config &>> ${VMNAME}.log
# Remove the unnecessary cloud init files
outputn "Cleaning up cloud-init files"
rm -f $USER_DATA $META_DATA $CI_ISO && ok
MAC=$(virsh dumpxml ${VMNAME} | awk -F\' '/mac address/ {print $2}')
output "MAC address: ${MAC}"
if [ -f "/var/lib/libvirt/dnsmasq/${BRIDGE}.status" ]
then
outputn "Waiting for domain to get an IP address"
while true
do
IP=$(grep -B1 $MAC /var/lib/libvirt/dnsmasq/$BRIDGE.status | head \
-n 1 | awk '{print $2}' | sed -e s/\"//g -e s/,//)
if [ "$IP" = "" ]
then
sleep 1
else
ok
break
fi
done
printf "\n"
check_delete_known_host
else
outputn "Bridge looks like a layer 2 bridge, get the domain's IP address from your DHCP server"
IP="<IP address>"
fi
printf "\n"
output "SSH to ${VMNAME}: 'ssh ${LOGIN_USER}@${IP}' or 'ssh ${LOGIN_USER}@${VMNAME}'"
CONSOLE=$(virsh domdisplay ${VMNAME})
# Workaround because VNC port number shown by virsh domdisplay is offset from 5900
if [ "${GRAPHICS}" = 'vnc' ]
then
CONSOLE_NO_PORT=$(echo $CONSOLE | cut -d ':' -f 1,2 -)
CONSOLE_PORT=$(expr 5900 + $(echo $CONSOLE | cut -d ':' -f 3 -))
output "Console at ${CONSOLE_NO_PORT}:${CONSOLE_PORT}"
else
output "Console at ${CONSOLE}"
fi
output "DONE"
popd
}
# Delete VM
function remove ()
{
# Parse command line arguments
while getopts ":l:L:hv" opt
do
case "$opt" in
l ) IMAGEDIR="${OPTARG}" ;;
L ) VMDIR="${OPTARG}" ;;
v ) VERBOSE=1 ;;
h ) usage ;;
* ) die "Unsupported option. Run 'kvm-install-vm help remove'." ;;
esac
done
shift $((OPTIND - 1))
if [ "$#" != 1 ]
then
printf "Please specify a single host to remove.\n"
printf "Run 'kvm-install-vm help remove' for usage.\n"
exit 1
else
VMNAME=$1
fi
delete_vm
}
function set_defaults ()
{
# Defaults are set here. Override using command line arguments.
AUTOSTART=false # Automatically start VM at boot time
CPUS=1 # Number of virtual CPUs
FEATURE=host # Use host cpu features to the guest
MEMORY=1024 # Amount of RAM in MB
DISK_SIZE="" # Disk Size in GB
DNSDOMAIN=example.local # DNS domain
GRAPHICS=spice # Graphics type or "auto"
RESIZE_DISK=false # Resize disk (boolean)
IMAGEDIR=${HOME}/virt/images # Directory to store images
VMDIR=${HOME}/virt/vms # Directory to store virtual machines
BRIDGE=virbr0 # Hypervisor bridge
PUBKEY="" # SSH public key
DISTRO=centos8 # Distribution
MACADDRESS="" # MAC Address
PORT=-1 # Console port
TIMEZONE=US/Eastern # Timezone
ADDITIONAL_USER=${USER} # User
ASSUME_YES=0 # Assume yes to prompts
ASSUME_NO=0 # Assume no to prompts
VERBOSE=0 # Verbosity
# Reset OPTIND
OPTIND=1
# Advanced hypervisor options. Override in ~/.kivrc if needed.
NETWORK_MODEL=virtio
NETWORK_EXTRA=""
DISK_BUS=virtio
DISK_EXTRA=""
CI_ISO_EXTRA=""
GRAPHICS_LISTEN=localhost
GRAPHICS_EXTRA=""
VIRT_INSTALL_EXTRA=""
}
function set_custom_defaults ()
{
# Source custom defaults, if set
if [ -f ~/.kivrc ];
then
source ${HOME}/.kivrc
fi
}
function create ()
{
# Parse command line arguments
while getopts ":b:c:d:D:f:g:i:k:l:L:m:M:p:s:t:T:u:ahynv" opt
do
case "$opt" in
a ) AUTOSTART=${OPTARG} ;;
b ) BRIDGE="${OPTARG}" ;;
c ) CPUS="${OPTARG}" ;;
d ) DISK_SIZE="${OPTARG}" ;;
D ) DNSDOMAIN="${OPTARG}" ;;
f ) FEATURE="${OPTARG}" ;;
g ) GRAPHICS="${OPTARG}" ;;
i ) IMAGE="${OPTARG}" ;;
k ) PUBKEY="${OPTARG}" ;;
l ) IMAGEDIR="${OPTARG}" ;;
L ) VMDIR="${OPTARG}" ;;
m ) MEMORY="${OPTARG}" ;;
M ) MACADDRESS="${OPTARG}" ;;
p ) PORT="${OPTARG}" ;;
s ) SCRIPTNAME="${OPTARG}" ;;
t ) DISTRO="${OPTARG}" ;;
T ) TIMEZONE="${OPTARG}" ;;
u ) ADDITIONAL_USER="${OPTARG}" ;;
y ) ASSUME_YES=1 ;;
n ) ASSUME_NO=1 ;;
v ) VERBOSE=1 ;;
h ) usage ;;
* ) die "Unsupported option. Run 'kvm-install-vm help create'." ;;
esac
done
shift $((OPTIND - 1))
# Resize disk if you specify a disk size either via cmdline option or .kivrc
if [ -n "${DISK_SIZE}" ]
then
RESIZE_DISK=true
DISK_SIZE="${DISK_SIZE}G" # Append 'G' for Gigabyte
fi
# Yes (-y) and No (-n) are mutually exclusive.
if [[ "${ASSUME_YES}" -eq 1 ]] && [[ "${ASSUME_NO}" -eq 1 ]]
then
printf "Please specify only one of -y or -n flags.\n"
exit 1
fi
# After all options are processed, make sure only one variable is left (vmname)
if [ "$#" != 1 ]
then
printf "Please specify a single host to create.\n"
printf "Run 'kvm-install-vm help create' for usage.\n"
exit 1
else
VMNAME=$1
fi
# Set cloud-init variables after VMNAME is assigned
USER_DATA=user-data
META_DATA=meta-data
CI_ISO=${VMNAME}-cidata.iso
# Check for ssh key
check_ssh_key
if [ ! -z "${IMAGE+x}" ]
then
output "Using custom QCOW2 image: ${IMAGE}."
OS_VARIANT="auto"
LOGIN_USER="<use the default account in your custom image>"
else
fetch_images
fi
# Verify the osinfo-db is up to date.
check_os_variant
# Check if domain already exists
domain_exists "${VMNAME}"
if [ "${DOMAIN_EXISTS}" -eq 1 ]; then
echo -n "[WARNING] ${VMNAME} already exists. Do you want to overwrite ${VMNAME} [y/N]? "
if [ "${ASSUME_YES}" -eq 1 ]; then
REPLY="y"
echo $REPLY
elif [ "${ASSUME_NO}" -eq 1 ]; then
REPLY="n"
echo $REPLY