apk-tools: implement compression of on-device scripts.tar
authorEric Fahlgren <[email protected]>
Sat, 15 Nov 2025 16:32:00 +0000 (08:32 -0800)
committerRobert Marko <[email protected]>
Tue, 18 Nov 2025 16:25:06 +0000 (17:25 +0100)
Default behavior for apk was to create an uncompressed scripts.tar
file.  Due to the structure of tar files, with fixed block
size and null padding, this file becomes very large on OpenWrt
installations where there are typically two scripts per package.
This could cause the raw tar file to easily grow to over 500KB,
whereas the compressed file is generally around 20-30KB.

When stored in the /rom partition of a squashfs device, the file
is compressed and this is not an issue.  But, as soon as you add
or delete a package, the scripts.tar file is fully expanded into
the /overlay partition and can cause issues on small-flash devices.

This issue was addressed in an upstream commit by detecting
whether the scripts.tar file is compressed (its name must be
exactly 'scripts.tar.gz'), and then retaining that compression by
reading/writing the file using a compressed stream.

This commit applies a cherrypicked patch for the upstream commit, and
compresses the scripts.tar during construction of the device rootfs.

Fixes: https://github.com/openwrt/openwrt/issues/17108
Link: https://gitlab.alpinelinux.org/alpine/apk-tools/-/commit/012cdcfdf981453b572a70c9568e8df6d0fe0ca1
Signed-off-by: Eric Fahlgren <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/20795
Signed-off-by: Robert Marko <[email protected]>
include/rootfs.mk
package/system/apk/Makefile
package/system/apk/patches/999-small-scripts-tar.patch [new file with mode: 0644]

index 26f249d8188378ed4b4613950c4a493b748bc53c..ebf4e406832163b06a87976c433b1fb6b9188bca 100644 (file)
@@ -78,7 +78,7 @@ define prepare_rootfs
                cd $(1); \
                if [ -n "$(CONFIG_USE_APK)" ]; then \
                        IPKG_POSTINST_PATH=./lib/apk/db/*.post-install; \
-                       $(STAGING_DIR_HOST)/bin/tar -C ./lib/apk/db/ -xf ./lib/apk/db/scripts.tar --wildcards "*.post-install"; \
+                       $(STAGING_DIR_HOST)/bin/tar -C ./lib/apk/db/ -xzf ./lib/apk/db/scripts.tar.gz --wildcards "*.post-install"; \
                else \
                        IPKG_POSTINST_PATH=./usr/lib/opkg/info/*.postinst; \
                fi; \
@@ -89,7 +89,7 @@ define prepare_rootfs
                                echo "postinst script $$script has failed with exit code $$ret" >&2; \
                                exit 1; \
                        fi; \
-                       [ -n "$(CONFIG_USE_APK)" ] && $(STAGING_DIR_HOST)/bin/tar --delete -f ./lib/apk/db/scripts.tar $$(basename $$script); \
+                       [ -n "$(CONFIG_USE_APK)" ] && $(STAGING_DIR_HOST)/bin/tar --delete -zf ./lib/apk/db/scripts.tar.gz $$(basename $$script); \
                done; \
                if [ -z "$(CONFIG_USE_APK)" ]; then \
                        $(if $(IB),,awk -i inplace \
index 53a9a6c0067ccf4e2c5cc7cb06d61b33346d2010..3e4f0c1466ef516843dfda7e7d95c66d280bbd9a 100644 (file)
@@ -1,7 +1,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=apk
-PKG_RELEASE:=1
+PKG_RELEASE:=2
 
 PKG_SOURCE_URL=https://gitlab.alpinelinux.org/alpine/apk-tools.git
 PKG_SOURCE_PROTO:=git
diff --git a/package/system/apk/patches/999-small-scripts-tar.patch b/package/system/apk/patches/999-small-scripts-tar.patch
new file mode 100644 (file)
index 0000000..ff93e82
--- /dev/null
@@ -0,0 +1,55 @@
+diff --git a/doc/apk.8.scd b/doc/apk.8.scd
+index bd15fe75d7a4..cbac8c6c8b7b 100644
+--- a/doc/apk.8.scd
++++ b/doc/apk.8.scd
+@@ -461,7 +461,8 @@ timeout 120
+ */lib/apk/db/installed*
+       Database of installed packages and their contents.
+-*/lib/apk/db/scripts.tar*
++*/lib/apk/db/scripts.tar*++
++*/lib/apk/db/scripts.tar.gz*
+       Collection of all package scripts from currently installed packages.
+ */lib/apk/db/triggers*
+diff --git a/src/apk_database.h b/src/apk_database.h
+index 9f8670ac8ebb..10f1414c9e06 100644
+--- a/src/apk_database.h
++++ b/src/apk_database.h
+@@ -202,6 +202,7 @@ struct apk_database {
+       unsigned int compat_depversions : 1;
+       unsigned int sorted_names : 1;
+       unsigned int sorted_installed_packages : 1;
++      unsigned int scripts_tar : 1;
+       struct apk_dependency_array *world;
+       struct apk_id_cache *id_cache;
+diff --git a/src/database.c b/src/database.c
+index cbe58ebbf885..cb1b709140af 100644
+--- a/src/database.c
++++ b/src/database.c
+@@ -1255,8 +1255,11 @@ static int apk_db_read_layer(struct apk_database *db, unsigned layer)
+       }
+       if (!(flags & APK_OPENF_NO_SCRIPTS)) {
+-              r = apk_tar_parse(apk_istream_from_file(fd, "scripts.tar"),
+-                                apk_read_script_archive_entry, db, db->id_cache);
++              struct apk_istream *is = apk_istream_from_file(fd, "scripts.tar");
++              if (!IS_ERR(is) || PTR_ERR(is) != -ENOENT) db->scripts_tar = 1;
++              else is = apk_istream_gunzip(apk_istream_from_file(fd, "scripts.tar.gz"));
++
++              r = apk_tar_parse(is, apk_read_script_archive_entry, db, db->id_cache);
+               if (!ret && r != -ENOENT) ret = r;
+       }
+@@ -2128,8 +2131,9 @@ static int apk_db_write_layers(struct apk_database *db)
+                       continue;
+               }
+               ld->installed = apk_ostream_to_file(ld->fd, "installed", 0644);
+-              ld->scripts   = apk_ostream_to_file(ld->fd, "scripts.tar", 0644);
+               ld->triggers  = apk_ostream_to_file(ld->fd, "triggers", 0644);
++              if (db->scripts_tar) ld->scripts = apk_ostream_to_file(ld->fd, "scripts.tar", 0644);
++              else ld->scripts = apk_ostream_gzip(apk_ostream_to_file(ld->fd, "scripts.tar.gz", 0644));
+               if (i == APK_DB_LAYER_ROOT)
+                       os = apk_ostream_to_file(db->root_fd, apk_world_file, 0644);