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