-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvmtree-vm.sh
executable file
·201 lines (183 loc) · 6.85 KB
/
vmtree-vm.sh
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
#!/bin/bash
# Needed for mapfile and advanced pattern matching
shopt -s lastpipe extglob
# Force location
cd "$(dirname "$0")" || exit
# Source env
source .env
# Script's first parameter is ssh pubkey name (username)
SSHUSER="$1"
shift
# Strip VM name of domain, then split on "-"
# XXX Should be a better way of splitting that doesn't mess with IFS
SAVEIFS="$IFS"
# Shellcheck thinks we don't want splitting, but we do.
# shellcheck disable=SC2206
IFS="-" PARTS=( ${1%%.*} )
IFS="$SAVEIFS"
unset SAVEIFS
# One word vmname or more?
if [[ ${#PARTS[@]} -eq 1 ]]; then
# One word vm name is a special case. We never try to launch it, just connect -- if the SSH key is in.
# One useful way to create one-word VMs is by starting them as `demo-vmname`, then using `lxc rename`.
REQUSER=""
REQVM="${PARTS[0]}"
REQIMAGE="${DEFAULTIMAGE:-ubuntu2404}"
REQETC=""
VM="$REQVM"
DISKPATH="/dev/null" # Won't count since we never launch, but anyway
printf "Trying to connect you directly to \"$REQVM\"...\n\n" >&2
else
# Friendlier variable
REQUSER="${PARTS[0]}"
REQVM="${PARTS[1]}"
REQIMAGE="${PARTS[2]:-${DEFAULTIMAGE:-ubuntu2404}}"
REQETC="${PARTS[3]}"
# Force "prefix-" to VM, but let anyone use "demo"
if [[ "$REQUSER" == "demo" ]]; then
# Load ALL keys
cat /vmtree/keys/* | mapfile -t PUBKEYS
# Force DISK to "demo"
DISK="demo"
else
# Load ONLY SSHUSER's key(s)
mapfile -t PUBKEYS <"/vmtree/keys/$SSHUSER"
DISK="$SSHUSER"
fi
# Build final physical variables
VM="$REQUSER-$REQVM"
DISKPATH="/vmtree/disks/$DISK"
echo "Just FYI - you have the following VMs here:" >&2
lxc list -c nst4,image.release,mcl "^${REQUSER}-" >&2
fi
# Images
declare -A images
# Best:
images["ubuntu2004"]="ubuntu:20.04" # Works 100%
images["ubuntu2204"]="ubuntu:22.04" # Works 100%
images["ubuntu2310"]="ubuntu:23.10" # Works 100%
images["ubuntu2404"]="ubuntu:24.04" # Works 100%
images["ubuntu2410"]="ubuntu-daily:o" # Works 100%, daily for testing
# Others:
images["alma8"]="images:almalinux/8/cloud" # Works, not thoroughly tested
images["alma9"]="images:almalinux/9/cloud" # Works, not thoroughly tested
images["centos8"]="images:centos/8-Stream/cloud" # Works, not thoroughly tested
images["centos9"]="images:centos/9-Stream/cloud" # Works, not thoroughly tested
images["debian11"]="images:debian/11/cloud" # First connect never works, SSH install takes time
images["debian12"]="images:debian/12/cloud" # First connect never works, SSH install takes time
images["rocky8"]="images:rockylinux/8/cloud" # Works, not thoroughly tested
images["rocky9"]="images:rockylinux/9/cloud" # Works, not thoroughly tested
images["suse"]="images:opensuse/tumbleweed/cloud" # Works, not thoroughly tested
images["suse154"]="images:opensuse/15.4/cloud" # Works, not thoroughly tested. Unfortunately there is no plain "15", so minor version will need to be updated
images["suse155"]="images:opensuse/15.5/cloud" # Works, not thoroughly tested. Unfortunately there is no plain "15", so minor version will need to be updated
images["oracle8"]="images:oracle/8/cloud" # Works, not thoroughly tested
images["oracle9"]="images:oracle/9/cloud" # Works, not thoroughly tested
# Tested NOT working:
#images["centos7"]="images:centos/7/cloud" # "requires a CGroupV1 host system"
#images["alpine"]="images:alpine/edge/cloud" # Needs manual enable of user account and shell change
#images["oracle7"]="images:oracle/7/cloud" # "requires a CGroupV1 host system"
echo -e "Available images (user-vmname-IMAGE): ${!images[*]}" >&2
IMAGE="${images[$REQIMAGE]:-ubuntu:24.04}"
# Show info
printf "Connecting SSHUSER=$SSHUSER VM=$VM IMAGE=$IMAGE DISK=$DISK\n\nThis VM's port 80 is: https://$VM.$DOMAIN/ ( $AUTHUSER / $AUTHPASS )\n\n" >&2
# Default lxc options
OPTS=("-c" "security.nesting=true" "-c" "linux.kernel_modules=overlay,nf_nat,ip_tables,ip6_tables,netlink_diag,br_netfilter,xt_conntrack,nf_conntrack,ip_vs,vxlan")
# REAL VM (as opposed to container) mode
# XXX EXPERIMENTAL!
if [[ "$REQETC" = "vm"* ]]; then
# This changes EVERYTHING (in OPTS)
OPTS=( "--vm")
# Set limits, if given. Enforce format
LIMIT="${REQETC#vm}"
# Allow only ONE DIGIT (and only a digit, preventing overuse and injection)
if [[ "$LIMIT" =~ ^[0-9]$ ]]; then
OPTS+=( "-c" "limits.cpu=2" "-c" "limits.memory=${LIMIT}GiB" )
fi
fi
# Print infos here.
# Maybe it gets read while the user is waiting.
cat >&2 <templates/motd
echo >&2 # empty line
# Does the VM exists?
if ! lxc info "$VM" >/dev/null 2>&1 ; then
# Sanity check
if [[ "$REQUSER" != "$SSHUSER" && "$REQUSER" != "demo" ]]; then
echo "ERROR: you can only start VMs called $SSHUSER-xxx and demo-xxx" >&2
exit 1
fi
# launch docker-capable vm
echo "Initializing" >&2
lxc init "${IMAGE}" "$VM" "${OPTS[@]}" >&2 </dev/null
# Mount disk if exists
if [[ -d "$DISKPATH" ]]; then
echo "Attaching disks/$DISK" >&2
lxc config device add "$VM" "$DISK" disk "source=$DISKPATH" "path=/persist" >&2
fi
echo "Cloud-config" >&2
# Apply cloud-config
lxc config set "$VM" user.user-data - >&2 <<EOF
#cloud-config
users:
- name: user
ssh_authorized_keys:
$(for pubkey in "${PUBKEYS[@]}"; do echo " - ${pubkey}"; done)
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
package_update: true
packages:
- bash-completion
- curl
- dnsutils
- git
- htop
- less
- openssh-server
- psmisc
- rsync
- screen
- socat
- tig
- unattended-upgrades
- unzip
- vim-nox
- wget
- zip
write_files:
- path: /etc/motd
content: |
============================ Mini-HOWTO ============================
Destroy this VM (~1 min): ..................... sudo touch /killme
Disable HTTP password protection (~1 min): .... sudo touch /nopassword
Make this VM survive the night: ............... sudo touch /nokill
====================================================================
#- path: /etc/docker/daemon.json
# content: |
# { "storage-driver": "overlay2" }
- path: /etc/sysctl.d/95-unprivileged-ports.conf
content: |
net.ipv4.ip_unprivileged_port_start=0
# XXX Next 3 lines are part of the workaround for https://github.com/canonical/lxd/issues/13389
- path: /etc/apparmor.d/local/runc
content: |
pivot_root,
bootcmd:
- [ "sysctl", "-w", "net.ipv4.ip_unprivileged_port_start=0" ]
runcmd:
# XXX Next 1 line is part of the workaround for https://github.com/canonical/lxd/issues/13389
- [ "systemctl", "reload", "apparmor.service" ]
EOF
fi
echo "Starting" >&2
lxc start "$VM" >/dev/null >&2
# Waiting for IP, for SSH port to open, and for '/run/nologin' to go away
echo -n "Waiting for ready" >&2
until (echo >"/dev/tcp/$VM.lxd/22") 2>/dev/null && ! lxc file pull "$VM/run/nologin" - >/dev/null 2>/dev/null; do
echo -n "." >&2
sleep 1
done
# Display IP
IPS="$(lxc list --format csv -c 4 "^${VM}$")"
echo " IPs: $IPS" >&2
# stdio fwd
echo "Connecting" >&2
nc "${VM}.lxd" 22