aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-19 12:12:00 +0200
committerDanilo M. <danix@danix.xyz>2026-09-19 12:12:00 +0200
commit3b58f17a3fc7f6aa50cfb8bdba14b2321abb4679 (patch)
treef57fb6a4a98ad8e263b4522de9ceb97a91108481
downloadinstall-compat32-3b58f17a3fc7f6aa50cfb8bdba14b2321abb4679.tar.gz
install-compat32-3b58f17a3fc7f6aa50cfb8bdba14b2321abb4679.zip
feat: add compat32ctl for Slackware64 -currentHEADmaster
Manage AlienBOB's compat32 package collection: mirror the -current tree with lftp, verify it against CHECKSUMS.md5, and install/update/remove the 32-bit layer with upgradepkg/removepkg. Includes a man page and a dry-run self-check.
-rw-r--r--LICENSE338
-rw-r--r--README.md112
-rwxr-xr-xcompat32ctl148
-rw-r--r--compat32ctl.8192
-rwxr-xr-xtest_compat32ctl.sh56
5 files changed, 846 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9efa6fb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,338 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Moe Ghoul>, 1 April 1989
+ Moe Ghoul, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7f85034
--- /dev/null
+++ b/README.md
@@ -0,0 +1,112 @@
+# compat32ctl
+
+Install, update and remove AlienBOB's `-compat32` package collection on a
+Slackware64 `-current` system.
+
+On `-current` the multilib capability is built into Slackware's own `gcc` and
+`glibc` packages (added 2024-08-30), so no third-party toolchain replacement is
+needed. What is still required is the layer of 32-bit compatibility packages,
+which this script manages from
+<https://slackware.nl/people/alien/multilib/current>.
+
+## Requirements
+
+- Slackware64 `-current`
+- `lftp` (mirroring), `md5sum` (verification)
+- Slackware `pkgtools` (`upgradepkg`, `removepkg`) for install/update/remove
+- root for any command that modifies the system (`sync` also writes to the
+ default cache directory, so run it as root too; `--dry-run` works unprivileged)
+
+## Usage
+
+```sh
+compat32ctl sync # download/refresh the package tree and verify checksums
+compat32ctl install # sync, then install compat32-tools and all -compat32 packages
+compat32ctl update # same as install (upgradepkg --install-new upgrades existing)
+compat32ctl remove # remove every installed compat32 package
+
+compat32ctl <command> -n # dry run: print the commands without executing them
+compat32ctl help
+```
+
+`install` and `update` are the same idempotent flow: `upgradepkg --install-new`
+upgrades packages that are already installed and adds any new ones, so the same
+command serves both first-time setup and later refreshes.
+
+## Configuration
+
+Overridable via environment variables:
+
+| Variable | Default | Purpose |
+|--------------|-----------------------------------------------------|-----------------------------|
+| `REPO_URL` | `https://slackware.nl/people/alien/multilib/current`| Remote package tree |
+| `MIRROR_DIR` | `/var/cache/compat32` | Local package cache |
+| `PKGDIR` | `/var/log/packages` | Installed-package database |
+
+Example: mirror to a staging directory instead of the system cache:
+
+```sh
+MIRROR_DIR=/srv/compat32 compat32ctl sync
+```
+
+## How it works
+
+- **sync** mirrors the remote tree with `lftp ... mirror -c -e -v` (incremental,
+ resumable, prunes locally deleted/stale files) into `MIRROR_DIR`, then
+ verifies the packages against the repository's `CHECKSUMS.md5` with
+ `md5sum -c --ignore-missing`.
+- **install/update** run `sync`, then
+ `upgradepkg --install-new` over `compat32-tools-*.tgz` and
+ `slackware64-compat32/*-compat32/*.t?z`.
+- **remove** deletes every installed package whose name contains `compat32`
+ (the `-compat32` libraries plus `compat32-tools`). No `gcc`/`glibc` rollback
+ is needed on `-current`, since those are Slackware's own packages.
+
+## Download progress
+
+When run on a terminal, `lftp` shows a live status line for the file being
+transferred (bytes, percentage, rate, ETA), and `mirror -v` prints a
+`Transferring file` line per file. Progress is only shown when the output is a
+terminal; under cron or a non-interactive pipe the transfer is silent.
+
+## Note for slackpkg users
+
+If you use `slackpkg`, add these lines to `/etc/slackpkg/blacklist` so it does
+not remove or replace the multilib packages:
+
+```
+[0-9]+alien
+[0-9]+compat32
+```
+
+## Man page
+
+`compat32ctl.8` documents the script. Install it into section 8 with:
+
+```sh
+install -Dm644 compat32ctl.8 /usr/man/man8/compat32ctl.8
+```
+
+Then read it with `man compat32ctl`.
+
+## Testing
+
+`test_compat32ctl.sh` runs unprivileged and exercises argument handling and the
+`--dry-run` previews, including the remove-discovery logic against a temporary
+package database:
+
+```sh
+./test_compat32ctl.sh
+```
+
+## License
+
+GPL v2.0 (see [LICENSE](LICENSE)).
+
+## Development Approach
+
+This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback.
+
+All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions.
+
+The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability.
diff --git a/compat32ctl b/compat32ctl
new file mode 100755
index 0000000..af3c212
--- /dev/null
+++ b/compat32ctl
@@ -0,0 +1,148 @@
+#!/bin/bash
+#
+# compat32ctl - install, update and remove AlienBOB's compat32 package
+# collection on Slackware64 -current.
+#
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+set -euo pipefail
+
+REPO_URL="${REPO_URL:-https://slackware.nl/people/alien/multilib/current}"
+MIRROR_DIR="${MIRROR_DIR:-/var/cache/compat32}"
+PKGDIR="${PKGDIR:-/var/log/packages}"
+
+DRY_RUN=0
+PROG="${0##*/}"
+
+usage() {
+ cat <<EOF
+Usage: $PROG <command> [-n|--dry-run]
+
+Commands:
+ sync Download/refresh the compat32 tree and verify its checksums
+ install Sync, then install or upgrade compat32-tools and every -compat32 package
+ update Same as install (kept as a distinct verb for intent)
+ remove Remove every installed compat32 package
+
+Options:
+ -n, --dry-run Show the commands that would run without executing them
+ -h, --help Show this help
+
+Environment:
+ REPO_URL Remote tree to sync (default: $REPO_URL)
+ MIRROR_DIR Local package cache (default: $MIRROR_DIR)
+ PKGDIR Installed-package db (default: $PKGDIR)
+EOF
+}
+
+die() {
+ printf '%s: %s\n' "$PROG" "$*" >&2
+ exit 1
+}
+
+require_cmd() {
+ [ "$DRY_RUN" -eq 1 ] && return 0
+ command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
+}
+
+require_root() {
+ [ "$DRY_RUN" -eq 1 ] && return 0
+ [ "$(id -u)" -eq 0 ] || die "must be run as root (or use --dry-run to preview)"
+}
+
+run() {
+ if [ "$DRY_RUN" -eq 1 ]; then
+ printf '+ %s\n' "$*"
+ else
+ "$@"
+ fi
+}
+
+mirror_and_verify() {
+ require_cmd lftp
+ require_cmd md5sum
+ require_root
+ printf '==> Syncing %s -> %s\n' "$REPO_URL" "$MIRROR_DIR"
+ run mkdir -p "$MIRROR_DIR"
+ run lftp -c "set cmd:show-status on; open $REPO_URL; mirror -c -e -v . $MIRROR_DIR"
+ printf '==> Verifying checksums\n'
+ if [ "$DRY_RUN" -eq 1 ]; then
+ printf '+ cd %s && md5sum -c --ignore-missing CHECKSUMS.md5\n' "$MIRROR_DIR"
+ else
+ ( cd "$MIRROR_DIR" && md5sum -c --ignore-missing CHECKSUMS.md5 )
+ fi
+}
+
+do_install() {
+ require_cmd upgradepkg
+ mirror_and_verify
+ printf '==> Installing or upgrading compat32 packages\n'
+ run upgradepkg --install-new "$MIRROR_DIR"/compat32-tools-*.tgz
+ run upgradepkg --install-new "$MIRROR_DIR"/slackware64-compat32/*-compat32/*.t?z
+}
+
+do_remove() {
+ require_cmd removepkg
+ require_root
+ local pkg names=()
+ for pkg in "$PKGDIR"/*compat32*; do
+ [ -e "$pkg" ] || continue
+ names+=("${pkg##*/}")
+ done
+ if [ "${#names[@]}" -eq 0 ]; then
+ printf 'No installed compat32 packages found.\n'
+ return 0
+ fi
+ printf '==> Removing %d compat32 package(s)\n' "${#names[@]}"
+ run removepkg "${names[@]}"
+}
+
+main() {
+ if [ "$#" -lt 1 ]; then
+ usage >&2
+ exit 1
+ fi
+
+ local cmd="$1"
+ shift
+
+ case "$cmd" in
+ -h|--help|help)
+ usage
+ exit 0
+ ;;
+ esac
+
+ while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -n|--dry-run) DRY_RUN=1 ;;
+ -h|--help) usage; exit 0 ;;
+ *) die "unknown option: $1" ;;
+ esac
+ shift
+ done
+
+ case "$cmd" in
+ sync) mirror_and_verify ;;
+ install) do_install ;;
+ update) do_install ;;
+ remove) do_remove ;;
+ *) usage >&2; die "unknown command: $cmd" ;;
+ esac
+}
+
+if [ "${BASH_SOURCE[0]}" = "$0" ]; then
+ main "$@"
+fi
diff --git a/compat32ctl.8 b/compat32ctl.8
new file mode 100644
index 0000000..e16387a
--- /dev/null
+++ b/compat32ctl.8
@@ -0,0 +1,192 @@
+.\" compat32ctl.8 - manual page for compat32ctl
+.\" Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+.\" SPDX-License-Identifier: GPL-2.0-only
+.TH COMPAT32CTL 8 "2026-09-19" "compat32ctl" "System Administration"
+.SH NAME
+compat32ctl \- manage AlienBOB's compat32 packages on Slackware64 \-current
+.SH SYNOPSIS
+.B compat32ctl
+.I command
+.RI [ options ]
+.SH DESCRIPTION
+.B compat32ctl
+installs, updates and removes AlienBOB's collection of 32\-bit
+compatibility packages (the
+.B \-compat32
+packages) on a Slackware64
+.B \-current
+system.
+.PP
+On
+.B \-current
+the multilib capability is provided by Slackware's own
+.BR gcc (1)
+and
+.BR glibc ,
+so no third\-party toolchain replacement is required; only the 32\-bit
+compatibility library layer is managed. Packages are sourced from
+.IR https://slackware.nl/people/alien/multilib/current .
+.PP
+The package tree is mirrored into a local cache, verified against the
+repository's
+.B CHECKSUMS.md5
+file, and installed with
+.BR upgradepkg (8).
+.SH COMMANDS
+.TP
+.B sync
+Mirror the remote package tree into
+.I MIRROR_DIR
+using
+.BR lftp (1)
+with
+.BR "mirror \-c \-e" ,
+then verify every downloaded file against
+.BR CHECKSUMS.md5 .
+.TP
+.B install
+Run
+.B sync
+and then install or upgrade
+.B compat32\-tools
+and every
+.B \-compat32
+package with
+.BR "upgradepkg \-\-install\-new" .
+.TP
+.B update
+Identical to
+.BR install .
+Provided as a distinct verb for intent; the underlying command is
+idempotent, upgrading packages that are already installed and adding new
+ones.
+.TP
+.B remove
+Remove every installed package whose name contains
+.BR compat32 ,
+covering both the
+.B \-compat32
+libraries and
+.BR compat32\-tools .
+Does not touch the network and does not roll back
+.BR gcc / glibc ,
+which are Slackware's own packages on
+.BR \-current .
+.TP
+.BR help ", " \-h ", " \-\-help
+Show the usage message.
+.SH OPTIONS
+.TP
+.BR \-n ", " \-\-dry\-run
+Print the commands that would be executed without running them. With
+this option the root and dependency checks are skipped, so the preview
+can be run as an unprivileged user.
+.TP
+.BR \-h ", " \-\-help
+Show the usage message.
+.SH ENVIRONMENT
+.TP
+.B REPO_URL
+Remote package tree to mirror. Default:
+.IR https://slackware.nl/people/alien/multilib/current .
+.TP
+.B MIRROR_DIR
+Local package cache. Default:
+.IR /var/cache/compat32 .
+.TP
+.B PKGDIR
+Directory holding installed\-package metadata, used by
+.BR remove .
+Default:
+.IR /var/log/packages .
+.SH FILES
+.TP
+.I /var/cache/compat32
+Default local mirror (see
+.BR MIRROR_DIR ).
+.TP
+.I /var/log/packages
+Installed\-package database (see
+.BR PKGDIR ).
+.TP
+.I /etc/slackpkg/blacklist
+When
+.BR slackpkg (8)
+is used, the entries
+.B [0\-9]+alien
+and
+.B [0\-9]+compat32
+should be present so slackpkg does not remove or replace the multilib
+packages.
+.SH EXIT STATUS
+.TP
+.B 0
+Success.
+.TP
+.B 1
+A usage error, a missing dependency, insufficient privileges, or a
+failing
+.BR upgradepkg (8)
+or
+.BR removepkg (8)
+invocation. The script runs with
+.BR "set \-e" ,
+so any failing step aborts the run.
+.SH NOTES
+.SS Progress
+When run on a terminal,
+.BR lftp (1)
+displays a live status line for the file being transferred, including
+bytes transferred, percentage, transfer rate and estimated time. The
+.B \-v
+option passed to
+.B mirror
+also prints a
+.B "Transferring file"
+line for each file. When the output is not a terminal (for example under
+cron), no progress is shown.
+.SS Requirements
+.BR lftp (1)
+and
+.B md5sum
+are required for
+.BR sync ;
+.BR upgradepkg (8)
+and
+.BR removepkg (8)
+are required for the respective commands. Root is required for any
+command that modifies the system.
+.SH EXAMPLES
+Preview a sync without changing anything:
+.PP
+.RS 4
+compat32ctl sync \-n
+.RE
+.PP
+Install or update the compat32 layer:
+.PP
+.RS 4
+compat32ctl install
+.RE
+.PP
+Remove the compat32 layer:
+.PP
+.RS 4
+compat32ctl remove
+.RE
+.PP
+Mirror into a staging directory instead of the system cache:
+.PP
+.RS 4
+MIRROR_DIR=/srv/compat32 compat32ctl sync
+.RE
+.SH SEE ALSO
+.BR lftp (1),
+.BR upgradepkg (8),
+.BR removepkg (8),
+.BR installpkg (8),
+.BR slackpkg (8)
+.SH LICENSE
+GNU General Public License, version 2 only.
+.SH AUTHOR
+Written by Danilo M. <danix@danix.xyz>.
diff --git a/test_compat32ctl.sh b/test_compat32ctl.sh
new file mode 100755
index 0000000..eadf304
--- /dev/null
+++ b/test_compat32ctl.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Runnable self-check for compat32ctl.
+# Exercises argument handling and the --dry-run previews, so it runs anywhere
+# (no root, no Slackware pkgtools required). Exits nonzero on any failure.
+#
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+# SPDX-License-Identifier: GPL-2.0-only
+#
+set -u
+cd "$(dirname "$0")"
+failed=0
+
+pass() { printf 'ok - %s\n' "$1"; }
+fail() { printf 'FAIL - %s\n' "$1"; failed=1; }
+
+# --- syntax ---------------------------------------------------------------
+if bash -n compat32ctl; then pass "syntax (bash -n)"; else fail "syntax (bash -n)"; fi
+
+# --- argument handling ----------------------------------------------------
+./compat32ctl >/dev/null 2>&1 && fail "no args -> usage, nonzero" || pass "no args -> usage, nonzero"
+./compat32ctl help >/dev/null 2>&1 && pass "help -> 0" || fail "help -> 0"
+./compat32ctl bogus >/dev/null 2>&1 && fail "unknown cmd -> nonzero" || pass "unknown cmd -> nonzero"
+
+# --- dry-run previews -----------------------------------------------------
+sync_out=$(./compat32ctl sync -n 2>&1)
+printf '%s' "$sync_out" | grep -q 'mirror -c -e -v' && pass "sync -n shows lftp mirror -v" || fail "sync -n shows lftp mirror -v"
+printf '%s' "$sync_out" | grep -q 'cmd:show-status on' && pass "sync -n enables lftp status" || fail "sync -n enables lftp status"
+printf '%s' "$sync_out" | grep -q 'md5sum -c --ignore-missing' && pass "sync -n shows md5 verify" || fail "sync -n shows md5 verify"
+
+install_out=$(./compat32ctl install -n 2>&1)
+printf '%s' "$install_out" | grep -q 'upgradepkg --install-new' && pass "install -n shows upgradepkg" || fail "install -n shows upgradepkg"
+[ "$install_out" = "$(./compat32ctl update -n 2>&1)" ] && pass "update -n matches install -n" || fail "update -n matches install -n"
+
+# --- remove refuses without root ------------------------------------------
+if [ "$(id -u)" -ne 0 ]; then
+ ./compat32ctl remove >/dev/null 2>&1 && fail "remove (real) refuses non-root" || pass "remove (real) refuses non-root"
+fi
+
+# --- remove discovery -----------------------------------------------------
+tmp=$(mktemp -d)
+touch "$tmp/aaa_libraries-compat32-16.0-x86_64-1compat32" \
+ "$tmp/compat32-tools-3.10-noarch-18alien" \
+ "$tmp/unrelated-1.0-x86_64-1"
+remove_out=$(PKGDIR="$tmp" ./compat32ctl remove -n 2>&1)
+rm -rf "$tmp"
+printf '%s' "$remove_out" | grep -q 'aaa_libraries-compat32-16.0-x86_64-1compat32' && pass "remove -n finds compat32 libs" || fail "remove -n finds compat32 libs"
+printf '%s' "$remove_out" | grep -q 'compat32-tools-3.10-noarch-18alien' && pass "remove -n finds compat32-tools" || fail "remove -n finds compat32-tools"
+printf '%s' "$remove_out" | grep -q 'unrelated' && fail "remove -n skips unrelated" || pass "remove -n skips unrelated"
+
+# --- remove on an empty package db ----------------------------------------
+tmp=$(mktemp -d)
+PKGDIR="$tmp" ./compat32ctl remove -n >/dev/null 2>&1 && pass "remove -n empty db -> 0" || fail "remove -n empty db -> 0"
+rm -rf "$tmp"
+
+exit $failed