Merge lp:~mwhudson/ubuntu-cdimage/netboot-tarball into lp:ubuntu-cdimage
- netboot-tarball
- Merge into mainline
Status: | Merged |
---|---|
Approved by: | Łukasz Zemczak |
Approved revision: | 2053 |
Merged at revision: | 2065 |
Proposed branch: | lp:~mwhudson/ubuntu-cdimage/netboot-tarball |
Merge into: | lp:ubuntu-cdimage |
Diff against target: |
384 lines (+190/-3) 5 files modified
lib/cdimage/build.py (+18/-0) lib/cdimage/livefs.py (+2/-1) lib/cdimage/tests/test_livefs.py (+2/-0) lib/cdimage/tests/test_tree.py (+45/-0) lib/cdimage/tree.py (+123/-2) |
To merge this branch: | bzr merge lp:~mwhudson/ubuntu-cdimage/netboot-tarball |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Łukasz Zemczak | Approve | ||
Review via email: mp+423215@code.launchpad.net |
Commit message
Publish netboot tarballs from livecd-rootfs
Description of the change
This is the companion part to https:/
It downloads the netboot tarball that livecd-rootfs change makes and publishes it alongside the ISO, rewriting the config files to refer to where the ISO is being published. It turns out that if you name various files *just so* then a lot of things are taken care of by general machinery, but that general machinery is pretty opaque. Oh well!
I've tried to keep the code fairly generic so if we want, say, the desktop build to start spitting out netboot tarballs no changes should be required here.
The glaring thing this is missing that has to be addressed before merge is release publication, which will have to rewrite the config files again (which probably means we should put the unmodified netboot tarball file in the published directory too but hide it from the index... hmm...).
The other thing that I want to do is generate some nice headers and footers for the netboot/ directory and generate some snippets somewhere that have example dhcpd and dnsmasq config.
I've tested code like this, but I refactored a bit before review and haven't tested exactly this code. So it might be a bit broken.
- 2040. By Michael Hudson-Doyle
-
download netboot tarball from livefs
- 2041. By Michael Hudson-Doyle
-
copy netboot tarball to location suitable for publication to find it
- 2042. By Michael Hudson-Doyle
-
copy any netboot tarball to target directory when publishing
- 2043. By Michael Hudson-Doyle
-
rewrite #ISOURL# to the url to the iso when publishing netboot tarball
- 2044. By Michael Hudson-Doyle
-
unpack netboot tarball during publication as well
Michael Hudson-Doyle (mwhudson) wrote : | # |
Łukasz Zemczak (sil2100) wrote : | # |
In general this feels good. Of course the re-tarring of the tarball is a bit controversial, but makes sense in this case - especially that per the specification we also want the contents to be untarred in the netboot/ directory anyway.
Left a few inline comments with some things that need changing though. Another thing that needs fixing is: we need unit tests for this new case. The cdimage codebase has usually quite a good test suit, so whenever we add something new, we should make sure it's tested as well. This way we can know if it works-ish.
That being said, I think currently the unit tests are failing! Mostly because of some issues with the simplestreams tests. I have a ticket open for that, so I'll try to fix those in the nearest week. For now, I'd recommend to just running a clean cdimage test run, noting which tests fail and then running with the new codebase + new tests ;p Sorry about that.
- 2045. By Michael Hudson-Doyle
-
add simple test to test_livefs.py
- 2046. By Michael Hudson-Doyle
-
add tests for and comments to publish_netboot
Michael Hudson-Doyle (mwhudson) wrote : | # |
> In general this feels good.
Thanks!
> Of course the re-tarring of the tarball is a bit
> controversial, but makes sense in this case - especially that per the
> specification we also want the contents to be untarred in the netboot/
> directory anyway.
Yeah, that's definitely feels a bit unusual. One consequence is that we'll have to do it again when doing the release publication -- not sure the code is set up for that though, does it recompute checksums as part of release publication? (I guess given that in general, not every arch gets published, it must?)
> Left a few inline comments with some things that need changing though. Another
> thing that needs fixing is: we need unit tests for this new case. The cdimage
> codebase has usually quite a good test suit, so whenever we add something new,
> we should make sure it's tested as well. This way we can know if it works-ish.
Good point. I've added test cases to test_livefs.py and test_tree.py. Couldn't figure out how to test the build.py change!
Michael Hudson-Doyle (mwhudson) : | # |
- 2047. By Michael Hudson-Doyle
-
release publication for netboot tarballs, maybe?
Michael Hudson-Doyle (mwhudson) wrote : | # |
I added some stuff around release publication but it doesn't get the release URLs right. I don't think I understand how the directories release publication puts things in map to URLs on releases.ubuntu.com yet...
- 2048. By Michael Hudson-Doyle
-
a little refactoring. add a comment where there is something wrong
Łukasz Zemczak (sil2100) wrote : | # |
This looks okay. There's two inline comments that can be addressed but don't have to be.
As for the publish bits - without any test coverage for those, it's hard to see if the release URLs are right or not. Can you maybe paste what end URLs are you getting in the tarball with the current changes? It would make it much easier for me to figure out how to make the mapping better.
Let's merge this tomorrow after we discuss those ^
- 2049. By Michael Hudson-Doyle
-
fix tarball save path
- 2050. By Michael Hudson-Doyle
-
more fixes
- 2051. By Michael Hudson-Doyle
-
rewrite SimpleReleaseTr
ee.url_ for_path a bit - 2052. By Michael Hudson-Doyle
-
logging, dry run
Michael Hudson-Doyle (mwhudson) wrote : | # |
> This looks okay. There's two inline comments that can be addressed but don't
> have to be.
Thanks. I actually hadn't pushed my most recent commit and it was interesting coming back to this code after a week or two, I think it helped me see the wood for the trees a bit.
> As for the publish bits - without any test coverage for those, it's hard to
> see if the release URLs are right or not. Can you maybe paste what end URLs
> are you getting in the tarball with the current changes? It would make it much
> easier for me to figure out how to make the mapping better.
I think I have this right now, lets see, for daily publication:
SUBPROJECT=live DIST=jammy PROJECT=
gives in the log:
Rewriting /home/ubuntu/
And publish-release seems to do the right thing now:
DIST=jammy for-project ubuntu publish-release --dry-run ubuntu-
...
Rewriting /home/ubuntu/
DIST=jammy for-project ubuntu publish-release --dry-run ubuntu-
...
Rewriting /home/ubuntu/
> Let's merge this tomorrow after we discuss those ^
Looking forward to it :-)
Michael Hudson-Doyle (mwhudson) : | # |
- 2053. By Michael Hudson-Doyle
-
sigh
Łukasz Zemczak (sil2100) wrote : | # |
Okay, I think this looks good now. So merging this branch causes some tests to fail, but I can fix those after merging this. They're trivial to fix it seems. So don't worry about those! But a note for the future: even if tests are not 100% green, be sure to check if your branch doesn't cause new test failures ;p (the test case should be much happier now though)
Łukasz Zemczak (sil2100) : | # |
Michael Hudson-Doyle (mwhudson) wrote : | # |
Thanks for the review and merge and sorry about the tests! I should see if I can write some tests for the release publication I guess.
Preview Diff
1 | === modified file 'lib/cdimage/build.py' |
2 | --- lib/cdimage/build.py 2022-06-02 05:07:57 +0000 |
3 | +++ lib/cdimage/build.py 2022-06-21 04:29:40 +0000 |
4 | @@ -523,6 +523,23 @@ |
5 | snap, "%s.%s.snap" % (output_prefix, snaptype)) |
6 | |
7 | |
8 | +def copy_netboot_tarballs(config): |
9 | + # cp $scratch/$arch.netboot.tar.gz $output/$series-netboot-$arch.tar.gz |
10 | + scratch_dir = os.path.join( |
11 | + config.root, "scratch", config.project, config.full_series, |
12 | + config.image_type) |
13 | + for arch in config.arches: |
14 | + netboot_path = os.path.join( |
15 | + live_output_directory(config), |
16 | + '%s.netboot.tar.gz' % (arch,)) |
17 | + if os.path.exists(netboot_path): |
18 | + output_dir = os.path.join(scratch_dir, "debian-cd", arch) |
19 | + shutil.copy2( |
20 | + netboot_path, os.path.join( |
21 | + output_dir, |
22 | + '%s-netboot-%s.tar.gz' % (config.series, arch))) |
23 | + |
24 | + |
25 | def _debootstrap_script(config): |
26 | return "usr/share/debootstrap/scripts/%s" % config.series |
27 | |
28 | @@ -724,6 +741,7 @@ |
29 | configure_splash(config) |
30 | |
31 | run_debian_cd(config) |
32 | + copy_netboot_tarballs(config) |
33 | fix_permissions(config) |
34 | |
35 | # Temporarily turned off for live builds. |
36 | |
37 | === modified file 'lib/cdimage/livefs.py' |
38 | --- lib/cdimage/livefs.py 2022-06-10 12:55:35 +0000 |
39 | +++ lib/cdimage/livefs.py 2022-06-21 04:29:40 +0000 |
40 | @@ -560,7 +560,7 @@ |
41 | "azure.device.tar.gz", "raspi2.device.tar.gz", "plano.device.tar.gz", |
42 | "tar.xz", "iso", "os.snap", "kernel.snap", "disk1.img.xz", |
43 | "dragonboard.kernel.snap", "raspi2.kernel.snap", |
44 | - "img.xz", "model-assertion", "qcow2", "yaml", |
45 | + "img.xz", "model-assertion", "qcow2", "yaml", "netboot.tar.gz" |
46 | ): |
47 | if item == "ext4" and arch == "armhf+nexus7": |
48 | for url in urls_for( |
49 | @@ -793,6 +793,7 @@ |
50 | elif download_live_items(config, arch, "squashfs"): |
51 | download_live_items(config, arch, "modules.squashfs") |
52 | download_live_items(config, arch, "yaml") |
53 | + download_live_items(config, arch, "netboot.tar.gz") |
54 | got_image = True |
55 | elif download_live_items(config, arch, "rootfs.tar.gz"): |
56 | got_image = True |
57 | |
58 | === modified file 'lib/cdimage/tests/test_livefs.py' |
59 | --- lib/cdimage/tests/test_livefs.py 2022-05-29 19:28:43 +0000 |
60 | +++ lib/cdimage/tests/test_livefs.py 2022-06-21 04:29:40 +0000 |
61 | @@ -1272,6 +1272,8 @@ |
62 | "bionic", "squashfs", ["installer.squashfs"]) |
63 | self.assert_server_live_download_items( |
64 | "bionic", "yaml", ["installer.yaml"]) |
65 | + self.assert_server_live_download_items( |
66 | + "bionic", "netboot.tar.gz", ["netboot.tar.gz"]) |
67 | |
68 | def test_download_live_server_boot_items(self): |
69 | self.assert_server_live_download_items( |
70 | |
71 | === modified file 'lib/cdimage/tests/test_tree.py' |
72 | --- lib/cdimage/tests/test_tree.py 2022-05-29 19:39:25 +0000 |
73 | +++ lib/cdimage/tests/test_tree.py 2022-06-21 04:29:40 +0000 |
74 | @@ -24,10 +24,12 @@ |
75 | from html.parser import HTMLParser |
76 | except ImportError: |
77 | from HTMLParser import HTMLParser |
78 | +import io |
79 | import os |
80 | import shutil |
81 | import sys |
82 | from textwrap import dedent |
83 | +import tarfile |
84 | import traceback |
85 | |
86 | try: |
87 | @@ -891,6 +893,46 @@ |
88 | target_dir, "%s-desktop-i386.iso.zsync" % self.config.series), |
89 | "%s-desktop-i386.iso" % self.config.series) |
90 | |
91 | + def test_publish_netboot(self): |
92 | + publisher = self.make_publisher("ubuntu-server", "daily-live") |
93 | + source_dir = publisher.image_output("amd64") |
94 | + tarname = "%s-netboot-amd64.tar.gz" % self.config.series |
95 | + isoname = "%s-live-server-amd64.iso" % self.config.series |
96 | + date = "20201215" |
97 | + os.makedirs(source_dir) |
98 | + with tarfile.open(os.path.join(source_dir, tarname), 'w:gz') as tf: |
99 | + ti = tarfile.TarInfo('config.in') |
100 | + content = b'v=#ISOURL#' |
101 | + ti.size = len(content) |
102 | + tf.addfile(ti, io.BytesIO(content)) |
103 | + target_dir = os.path.join(publisher.publish_base, date) |
104 | + target_image = os.path.join(target_dir, isoname) |
105 | + touch(target_image) |
106 | + |
107 | + publisher.publish_netboot("amd64", target_image) |
108 | + |
109 | + self.assertCountEqual([ |
110 | + tarname, |
111 | + isoname, |
112 | + "netboot", |
113 | + ], os.listdir(target_dir)) |
114 | + self.assertCountEqual([ |
115 | + "config", |
116 | + ], os.listdir(os.path.join(target_dir, "netboot"))) |
117 | + with open(os.path.join(target_dir, "netboot/config")) as fp: |
118 | + self.assertEqual( |
119 | + 'v=https://%s/ubuntu-server/daily-live/%s/%s' % ( |
120 | + publisher.tree.site_name, date, isoname), |
121 | + fp.read()) |
122 | + with tarfile.open(os.path.join(target_dir, tarname), 'r:gz') as tf: |
123 | + for ti in tf: |
124 | + self.assertEqual(ti.name, 'config') |
125 | + self.assertEqual( |
126 | + ('v=https://%s/ubuntu-server/daily-live/%s/%s' % ( |
127 | + publisher.tree.site_name, date, isoname) |
128 | + ).encode('utf-8'), |
129 | + tf.extractfile(ti).read()) |
130 | + |
131 | @mock.patch("cdimage.osextras.find_on_path", return_value=True) |
132 | @mock.patch("cdimage.tree.DailyTreePublisher.detect_image_extension", |
133 | return_value="img.xz") |
134 | @@ -2084,6 +2126,9 @@ |
135 | def test_publish_core_binary(self): |
136 | pass |
137 | |
138 | + def test_publish_netboot(self): |
139 | + pass |
140 | + |
141 | def test_publish_appliance_binary(self): |
142 | pass |
143 | |
144 | |
145 | === modified file 'lib/cdimage/tree.py' |
146 | --- lib/cdimage/tree.py 2022-06-08 08:29:32 +0000 |
147 | +++ lib/cdimage/tree.py 2022-06-21 04:29:40 +0000 |
148 | @@ -20,6 +20,7 @@ |
149 | from __future__ import print_function |
150 | |
151 | import errno |
152 | +import io |
153 | from itertools import count |
154 | from optparse import OptionParser |
155 | import os |
156 | @@ -33,6 +34,7 @@ |
157 | import stat |
158 | import subprocess |
159 | import sys |
160 | +import tarfile |
161 | from textwrap import dedent |
162 | import time |
163 | import traceback |
164 | @@ -93,6 +95,35 @@ |
165 | subprocess.check_call(command) |
166 | |
167 | |
168 | +def rewrite_and_unpack_tarball(dry_run, source_path, target_path, iso_url): |
169 | + logger.info( |
170 | + "Rewriting %s to %s with iso_url=%s", |
171 | + source_path, target_path, iso_url) |
172 | + if dry_run: |
173 | + return |
174 | + iso_url_b = iso_url.encode('utf-8') |
175 | + netboot_dir = os.path.join(os.path.dirname(target_path), 'netboot') |
176 | + osextras.ensuredir(netboot_dir) |
177 | + |
178 | + with tarfile.open(source_path) as inf: |
179 | + with tarfile.open(target_path, 'w:gz') as outf: |
180 | + for ti in inf: |
181 | + if ti.name.endswith('.in'): |
182 | + new_ti = inf.getmember(ti.name) |
183 | + new_ti.name = ti.name[:-3] |
184 | + content = inf.extractfile(ti).read() |
185 | + content = content.replace(b"#ISOURL#", iso_url_b) |
186 | + new_ti.size = len(content) |
187 | + with open( |
188 | + os.path.join(netboot_dir, new_ti.name), |
189 | + 'wb') as fp: |
190 | + fp.write(content) |
191 | + outf.addfile(new_ti, io.BytesIO(content)) |
192 | + else: |
193 | + inf.extract(ti, netboot_dir) |
194 | + outf.addfile(ti, inf.extractfile(ti)) |
195 | + |
196 | + |
197 | class Tree: |
198 | """A publication tree.""" |
199 | |
200 | @@ -168,6 +199,13 @@ |
201 | """Return the public host name corresponding to this tree.""" |
202 | raise NotImplementedError |
203 | |
204 | + def url_for_path(self, path): |
205 | + """Return the public URL for the file at `path`. |
206 | + |
207 | + `path` must be under self.directory. |
208 | + """ |
209 | + raise NotImplementedError |
210 | + |
211 | def path_to_manifest(self, path): |
212 | """Return a manifest file entry for a tree-relative path. |
213 | |
214 | @@ -487,6 +525,8 @@ |
215 | return "classroom server %s" % cd |
216 | else: |
217 | return "server install %s" % cd |
218 | + elif publish_type == "netboot": |
219 | + return "netboot tarball" |
220 | elif publish_type == "legacy-server": |
221 | return "legacy server install %s" % cd |
222 | elif publish_type == "serveraddon": |
223 | @@ -579,6 +619,10 @@ |
224 | sentences.append( |
225 | "The install %s allows you to install %s permanently on a " |
226 | "computer." % (cd, capproject)) |
227 | + elif publish_type == "netboot": |
228 | + sentences.append( |
229 | + "The netboot tarball contains files needed to boot the %s " |
230 | + "installer over the network." % (capproject,)) |
231 | elif publish_type == "alternate": |
232 | sentences.append( |
233 | "The alternate install %s allows you to perform certain " |
234 | @@ -1121,6 +1165,7 @@ |
235 | all_publish_types = ( |
236 | "live", "desktop", |
237 | "live-server", |
238 | + "netboot", |
239 | "legacy-server", |
240 | "server", "install", "alternate", |
241 | "serveraddon", "addon", |
242 | @@ -1653,7 +1698,8 @@ |
243 | print("ReadmeName FOOTER.html", file=htaccess) |
244 | print( |
245 | "IndexIgnore .htaccess HEADER.html FOOTER.html " |
246 | - "published-ec2-daily.txt published-ec2-release.txt", |
247 | + "published-ec2-daily.txt published-ec2-release.txt " |
248 | + ".*.tar.gz", |
249 | file=htaccess) |
250 | print( |
251 | "IndexOptions NameWidth=* DescriptionWidth=* " |
252 | @@ -1760,6 +1806,16 @@ |
253 | def site_name(self): |
254 | return "cdimage.ubuntu.com" |
255 | |
256 | + def url_for_path(self, path): |
257 | + logger.info( |
258 | + "url_for_path(%s), self.directory = %s", path, self.directory) |
259 | + if not path.startswith(self.directory): |
260 | + raise Exception( |
261 | + "url_for_path(%r) did not start with self.directory (%r)" |
262 | + % (path, self.directory)) |
263 | + url_path = path[len(self.directory):].lstrip('/') |
264 | + return "https://%s/%s" % (self.site_name, url_path) |
265 | + |
266 | def manifest_files(self): |
267 | """Yield all the files to include in a manifest of this tree.""" |
268 | seen_inodes = [] |
269 | @@ -2002,6 +2058,30 @@ |
270 | for line in jigdo_in: |
271 | jigdo_out.write(line.replace(from_line, to_line)) |
272 | |
273 | + def publish_netboot(self, arch, image_path): |
274 | + # Publishing a netboot tarball is a bit more complicated than |
275 | + # just copying it into place, as we also unpack it into a |
276 | + # netboot/ directory and replace rewrite any foo.cfg.in files |
277 | + # referencing #ISOURL# to foo.cfg referencing the actual URL |
278 | + # of the image. |
279 | + # |
280 | + # We also save a copy of the netboot tarball so we can rewrite |
281 | + # it again during release publication. |
282 | + tarname = "%s-netboot-%s.tar.gz" % (self.config.series, arch) |
283 | + source_path = os.path.join(self.image_output(arch), tarname) |
284 | + if not os.path.exists(source_path): |
285 | + return |
286 | + |
287 | + save_target_path = os.path.join( |
288 | + os.path.dirname(image_path), '.' + tarname) |
289 | + target_path = os.path.join(os.path.dirname(image_path), tarname) |
290 | + |
291 | + shutil.move(source_path, save_target_path) |
292 | + |
293 | + rewrite_and_unpack_tarball( |
294 | + False, save_target_path, target_path, |
295 | + self.tree.url_for_path(image_path)) |
296 | + |
297 | def publish_binary(self, publish_type, arch, date): |
298 | in_prefix = "%s-%s-%s" % (self.config.series, publish_type, arch) |
299 | if publish_type == "live-core": |
300 | @@ -2024,9 +2104,11 @@ |
301 | logger.info("Publishing %s ..." % arch) |
302 | osextras.ensuredir(target_dir) |
303 | extension = self.detect_image_extension(source_prefix) |
304 | + target_path = "%s.%s" % (target_prefix, extension) |
305 | shutil.move( |
306 | "%s.%s" % (source_prefix, self.source_extension), |
307 | - "%s.%s" % (target_prefix, extension)) |
308 | + target_path) |
309 | + self.publish_netboot(arch, target_path) |
310 | if os.path.exists("%s.list" % source_prefix): |
311 | shutil.move("%s.list" % source_prefix, "%s.list" % target_prefix) |
312 | self.checksum_dirs.append(source_dir) |
313 | @@ -2938,6 +3020,12 @@ |
314 | directory = os.path.join(config.root, "www", "simple") |
315 | super(SimpleReleaseTree, self).__init__(config, directory) |
316 | |
317 | + def url_for_path(self, path): |
318 | + series = self.config["DIST"] |
319 | + version = getattr(series, "pointversion", series.version) |
320 | + basename = os.path.basename(path) |
321 | + return "https://%s/%s/%s" % (self.site_name, version, basename) |
322 | + |
323 | def get_publisher(self, image_type, official, status=None, dry_run=False): |
324 | return SimpleReleasePublisher( |
325 | self, image_type, official, status=status, dry_run=dry_run) |
326 | @@ -3200,6 +3288,24 @@ |
327 | def want_torrent(self, publish_type): |
328 | raise NotImplementedError |
329 | |
330 | + def publish_release_netboot(self, daily_dir, prefix, arch, image_path): |
331 | + """Publish release images for a single architecture.""" |
332 | + logger.info("Copying netboot-%s image ..." % (arch, )) |
333 | + |
334 | + source_tarname = ".%s-netboot-%s.tar.gz" % (self.config.series, arch) |
335 | + source_tarpath = os.path.join(daily_dir, source_tarname) |
336 | + |
337 | + if not os.path.exists(source_tarpath): |
338 | + return |
339 | + |
340 | + target_tarname = "%s-netboot-%s.tar.gz" % (prefix, arch) |
341 | + target_tarpath = os.path.join( |
342 | + os.path.dirname(image_path), target_tarname) |
343 | + |
344 | + rewrite_and_unpack_tarball( |
345 | + self.dry_run, source_tarpath, target_tarpath, |
346 | + self.tree.url_for_path(image_path)) |
347 | + |
348 | def publish_release_arch(self, source, date, publish_type, arch): |
349 | """Publish release images for a single architecture.""" |
350 | logger.info("Copying %s-%s image ..." % (publish_type, arch)) |
351 | @@ -3236,9 +3342,12 @@ |
352 | return os.path.join( |
353 | torrent_dir, "%s%s%s" % (base_plain, sep, ext)) |
354 | |
355 | + main_img = None |
356 | + |
357 | for ext in ("iso", "img", "img.gz", "img.xz", "tar.gz", "img.tar.gz", |
358 | "tar.xz"): |
359 | if os.path.exists(daily(ext)): |
360 | + main_img = daily(ext) |
361 | break |
362 | else: |
363 | return |
364 | @@ -3254,8 +3363,20 @@ |
365 | self.copy(daily(ext), pool(ext)) |
366 | if self.want_dist: |
367 | self.symlink(pool(ext), dist(ext)) |
368 | + if daily(ext) == main_img: |
369 | + self.publish_release_netboot( |
370 | + os.path.dirname(daily(ext)), |
371 | + prefix_status, |
372 | + arch, |
373 | + dist(ext)) |
374 | if self.want_full: |
375 | self.copy(daily(ext), full(ext)) |
376 | + if daily(ext) == main_img: |
377 | + self.publish_release_netboot( |
378 | + os.path.dirname(daily(ext)), |
379 | + prefix, |
380 | + arch, |
381 | + full(ext)) |
382 | |
383 | for ext in ( |
384 | "initrd-ec2", "initrd-virtual", "vmlinuz-ec2", "vmlinuz-virtual", |
I've tested this now and it seems to work! I uploaded the result of a run to https:/ /people. canonical. com/~mwh/ 20220525/ and it all looks fairly plausible to me (don't try to use the ISOs in that page, I shrank them to 100M before upload)