1#! /bin/sh -e
2
3set -o pipefail
4#set -x
5
6stateDir=${TMPDIR:-/tmp}/ec2-image
7echo "keeping state in $stateDir"
8mkdir -p $stateDir
9
10version=$(nix-instantiate --eval --strict '<nixpkgs>' -A lib.nixpkgsVersion | sed s/'"'//g)
11major=${version:0:5}
12echo "NixOS version is $version ($major)"
13
14rm -f ec2-amis.nix
15
16types="hvm pv"
17stores="ebs s3"
18regions="eu-west-1 eu-central-1 us-east-1 us-west-1 us-west-2 ap-southeast-1 ap-southeast-2 ap-northeast-1 ap-northeast-2 sa-east-1 ap-south-1"
19
20for type in $types; do
21 link=$stateDir/$type
22 imageFile=$link/nixos.qcow2
23 system=x86_64-linux
24 arch=x86_64
25
26 # Build the image.
27 if ! [ -L $link ]; then
28 if [ $type = pv ]; then hvmFlag=false; else hvmFlag=true; fi
29
30 echo "building image type '$type'..."
31 nix-build -o $link \
32 '<nixpkgs/nixos>' \
33 -A config.system.build.amazonImage \
34 --arg configuration "{ imports = [ <nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix> ]; ec2.hvm = $hvmFlag; }"
35 fi
36
37 for store in $stores; do
38
39 bucket=nixos-amis
40 bucketDir="$version-$type-$store"
41
42 prevAmi=
43 prevRegion=
44
45 for region in $regions; do
46
47 name=nixos-$version-$arch-$type-$store
48 description="NixOS $system $version ($type-$store)"
49
50 amiFile=$stateDir/$region.$type.$store.ami-id
51
52 if ! [ -e $amiFile ]; then
53
54 echo "doing $name in $region..."
55
56 if [ -n "$prevAmi" ]; then
57 ami=$(aws ec2 copy-image \
58 --region "$region" \
59 --source-region "$prevRegion" --source-image-id "$prevAmi" \
60 --name "$name" --description "$description" | json -q .ImageId)
61 if [ "$ami" = null ]; then break; fi
62 else
63
64 if [ $store = s3 ]; then
65
66 # Bundle the image.
67 imageDir=$stateDir/$type-bundled
68
69 # Convert the image to raw format.
70 rawFile=$stateDir/$type.raw
71 if ! [ -e $rawFile ]; then
72 qemu-img convert -f qcow2 -O raw $imageFile $rawFile.tmp
73 mv $rawFile.tmp $rawFile
74 fi
75
76 if ! [ -d $imageDir ]; then
77 rm -rf $imageDir.tmp
78 mkdir -p $imageDir.tmp
79 ec2-bundle-image \
80 -d $imageDir.tmp \
81 -i $rawFile --arch $arch \
82 --user "$AWS_ACCOUNT" -c "$EC2_CERT" -k "$EC2_PRIVATE_KEY"
83 mv $imageDir.tmp $imageDir
84 fi
85
86 # Upload the bundle to S3.
87 if ! [ -e $imageDir/uploaded ]; then
88 echo "uploading bundle to S3..."
89 ec2-upload-bundle \
90 -m $imageDir/$type.raw.manifest.xml \
91 -b "$bucket/$bucketDir" \
92 -a "$AWS_ACCESS_KEY_ID" -s "$AWS_SECRET_ACCESS_KEY" \
93 --location EU
94 touch $imageDir/uploaded
95 fi
96
97 extraFlags="--image-location $bucket/$bucketDir/$type.raw.manifest.xml"
98
99 else
100
101 # Convert the image to vhd format so we don't have
102 # to upload a huge raw image.
103 vhdFile=$stateDir/$type.vhd
104 if ! [ -e $vhdFile ]; then
105 qemu-img convert -f qcow2 -O vpc $imageFile $vhdFile.tmp
106 mv $vhdFile.tmp $vhdFile
107 fi
108
109 vhdFileLogicalBytes="$(qemu-img info "$vhdFile" | grep ^virtual\ size: | cut -f 2 -d \( | cut -f 1 -d \ )"
110 vhdFileLogicalGigaBytes=$(((vhdFileLogicalBytes-1)/1024/1024/1024+1)) # Round to the next GB
111
112 echo "Disk size is $vhdFileLogicalBytes bytes. Will be registered as $vhdFileLogicalGigaBytes GB."
113
114 taskId=$(cat $stateDir/$region.$type.task-id 2> /dev/null || true)
115 volId=$(cat $stateDir/$region.$type.vol-id 2> /dev/null || true)
116 snapId=$(cat $stateDir/$region.$type.snap-id 2> /dev/null || true)
117
118 # Import the VHD file.
119 if [ -z "$snapId" -a -z "$volId" -a -z "$taskId" ]; then
120 echo "importing $vhdFile..."
121 taskId=$(ec2-import-volume $vhdFile --no-upload -f vhd \
122 -O "$AWS_ACCESS_KEY_ID" -W "$AWS_SECRET_ACCESS_KEY" \
123 -o "$AWS_ACCESS_KEY_ID" -w "$AWS_SECRET_ACCESS_KEY" \
124 --region "$region" -z "${region}a" \
125 --bucket "$bucket" --prefix "$bucketDir/" \
126 | tee /dev/stderr \
127 | sed 's/.*\(import-vol-[0-9a-z]\+\).*/\1/ ; t ; d')
128 echo -n "$taskId" > $stateDir/$region.$type.task-id
129 fi
130
131 if [ -z "$snapId" -a -z "$volId" ]; then
132 ec2-resume-import $vhdFile -t "$taskId" --region "$region" \
133 -O "$AWS_ACCESS_KEY_ID" -W "$AWS_SECRET_ACCESS_KEY" \
134 -o "$AWS_ACCESS_KEY_ID" -w "$AWS_SECRET_ACCESS_KEY"
135 fi
136
137 # Wait for the volume creation to finish.
138 if [ -z "$snapId" -a -z "$volId" ]; then
139 echo "waiting for import to finish..."
140 while true; do
141 volId=$(aws ec2 describe-conversion-tasks --conversion-task-ids "$taskId" --region "$region" | jq -r .ConversionTasks[0].ImportVolume.Volume.Id)
142 if [ "$volId" != null ]; then break; fi
143 sleep 10
144 done
145
146 echo -n "$volId" > $stateDir/$region.$type.vol-id
147 fi
148
149 # Delete the import task.
150 if [ -n "$volId" -a -n "$taskId" ]; then
151 echo "removing import task..."
152 ec2-delete-disk-image -t "$taskId" --region "$region" \
153 -O "$AWS_ACCESS_KEY_ID" -W "$AWS_SECRET_ACCESS_KEY" \
154 -o "$AWS_ACCESS_KEY_ID" -w "$AWS_SECRET_ACCESS_KEY" || true
155 rm -f $stateDir/$region.$type.task-id
156 fi
157
158 # Create a snapshot.
159 if [ -z "$snapId" ]; then
160 echo "creating snapshot..."
161 snapId=$(aws ec2 create-snapshot --volume-id "$volId" --region "$region" --description "$description" | jq -r .SnapshotId)
162 if [ "$snapId" = null ]; then exit 1; fi
163 echo -n "$snapId" > $stateDir/$region.$type.snap-id
164 fi
165
166 # Wait for the snapshot to finish.
167 echo "waiting for snapshot to finish..."
168 while true; do
169 status=$(aws ec2 describe-snapshots --snapshot-ids "$snapId" --region "$region" | jq -r .Snapshots[0].State)
170 if [ "$status" = completed ]; then break; fi
171 sleep 10
172 done
173
174 # Delete the volume.
175 if [ -n "$volId" ]; then
176 echo "deleting volume..."
177 aws ec2 delete-volume --volume-id "$volId" --region "$region" || true
178 rm -f $stateDir/$region.$type.vol-id
179 fi
180
181 blockDeviceMappings="DeviceName=/dev/sda1,Ebs={SnapshotId=$snapId,VolumeSize=$vhdFileLogicalGigaBytes,DeleteOnTermination=true,VolumeType=gp2}"
182 extraFlags=""
183
184 if [ $type = pv ]; then
185 extraFlags+=" --root-device-name /dev/sda1"
186 else
187 extraFlags+=" --root-device-name /dev/sda1"
188 extraFlags+=" --sriov-net-support simple"
189 extraFlags+=" --ena-support"
190 fi
191
192 blockDeviceMappings+=" DeviceName=/dev/sdb,VirtualName=ephemeral0"
193 blockDeviceMappings+=" DeviceName=/dev/sdc,VirtualName=ephemeral1"
194 blockDeviceMappings+=" DeviceName=/dev/sdd,VirtualName=ephemeral2"
195 blockDeviceMappings+=" DeviceName=/dev/sde,VirtualName=ephemeral3"
196 fi
197
198 if [ $type = hvm ]; then
199 extraFlags+=" --sriov-net-support simple"
200 extraFlags+=" --ena-support"
201 fi
202
203 # Register the AMI.
204 if [ $type = pv ]; then
205 kernel=$(aws ec2 describe-images --owner amazon --filters "Name=name,Values=pv-grub-hd0_1.04-$arch.gz" | jq -r .Images[0].ImageId)
206 if [ "$kernel" = null ]; then break; fi
207 echo "using PV-GRUB kernel $kernel"
208 extraFlags+=" --virtualization-type paravirtual --kernel $kernel"
209 else
210 extraFlags+=" --virtualization-type hvm"
211 fi
212
213 ami=$(aws ec2 register-image \
214 --name "$name" \
215 --description "$description" \
216 --region "$region" \
217 --architecture "$arch" \
218 --block-device-mappings $blockDeviceMappings \
219 $extraFlags | jq -r .ImageId)
220 if [ "$ami" = null ]; then break; fi
221 fi
222
223 echo -n "$ami" > $amiFile
224 echo "created AMI $ami of type '$type' in $region..."
225
226 else
227 ami=$(cat $amiFile)
228 fi
229
230 echo "region = $region, type = $type, store = $store, ami = $ami"
231
232 if [ -z "$prevAmi" ]; then
233 prevAmi="$ami"
234 prevRegion="$region"
235 fi
236 done
237
238 done
239
240done
241
242for type in $types; do
243 link=$stateDir/$type
244 system=x86_64-linux
245 arch=x86_64
246
247 for store in $stores; do
248
249 for region in $regions; do
250
251 name=nixos-$version-$arch-$type-$store
252 amiFile=$stateDir/$region.$type.$store.ami-id
253 ami=$(cat $amiFile)
254
255 echo "region = $region, type = $type, store = $store, ami = $ami"
256
257 echo -n "waiting for AMI..."
258 while true; do
259 status=$(aws ec2 describe-images --image-ids "$ami" --region "$region" | jq -r .Images[0].State)
260 if [ "$status" = available ]; then break; fi
261 sleep 10
262 echo -n '.'
263 done
264 echo
265
266 # Make the image public.
267 aws ec2 modify-image-attribute \
268 --image-id "$ami" --region "$region" --launch-permission 'Add={Group=all}'
269
270 echo " \"$major\".$region.$type-$store = \"$ami\";" >> ec2-amis.nix
271 done
272
273 done
274
275done