├── .dockerignore ├── .gitignore ├── apk-motd ├── Dockerfile └── flatten.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | -------------------------------------------------------------------------------- /apk-motd: -------------------------------------------------------------------------------- 1 | Welcome to Alpine! 2 | 3 | You can install packages with: apk add 4 | 5 | You may change this message by editing /etc/motd. 6 | 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/386 docker.io/library/alpine:3.19.0 AS ish-alpine 2 | 3 | FROM ish-alpine 4 | LABEL ish.export=appstore-apk.tar.gz 5 | RUN mkdir /ish && touch /ish/version 6 | RUN echo 31900 > /ish/apk-version 7 | COPY apk-motd /etc/motd 8 | -------------------------------------------------------------------------------- /flatten.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import os 4 | import sys 5 | import tarfile 6 | 7 | WHITEOUT = '.wh.' 8 | WHITEOUT_OPAQUE = '.wh..wh..opq' 9 | 10 | def flatten(image, output): 11 | manifest = json.load(image.extractfile('manifest.json')) 12 | assert len(manifest) == 1 13 | manifest = manifest[0] 14 | 15 | entries = {} 16 | layers = [tarfile.open(fileobj=image.extractfile(layer)) for layer in manifest['Layers']] 17 | for layer in layers: 18 | real_members = [] 19 | # process whiteout files 20 | for info in layer.getmembers(): 21 | info.name = './' + info.name 22 | dirname, sep, basename = info.name.rpartition('/') 23 | 24 | if basename == WHITEOUT_OPAQUE: 25 | for key in entries.keys(): 26 | if key.startswith(dirname+sep): 27 | del entries[key] 28 | elif basename.startswith(WHITEOUT): 29 | del entries[dirname+sep+basename[len(WHITEOUT):]] 30 | else: 31 | real_members.append(info) 32 | continue 33 | 34 | # real files 35 | for info in real_members: 36 | entries[info.name] = layer, info 37 | 38 | # need a root entry 39 | if './' not in entries: 40 | info = tarfile.TarInfo('./') 41 | info.type = tarfile.DIRTYPE 42 | info.mode = 0o755 43 | entries[info.name] = None, info 44 | 45 | for layer, info in entries.values(): 46 | fileobj = layer.extractfile(info) if info.isfile() else None 47 | info.mtime = 0 # make reproducible 48 | output.addfile(info, fileobj) 49 | 50 | for layer in layers: 51 | layer.close() 52 | 53 | with tarfile.open(sys.argv[1]) as image: 54 | with tarfile.open(fileobj=sys.stdout.buffer, mode='w|') as output: 55 | flatten(image, output) 56 | --------------------------------------------------------------------------------