Restart fips and the gateway when an apk upgrade replaces them

apk-tools v3 runs only the incoming package's pre-upgrade and post-upgrade
scripts on an upgrade, and the .apk registered neither. An upgrade replaced
the binaries and init scripts on disk but left procd running the old fips
and fips-gateway processes until a reboot or a manual restart.

The .apk now registers pre-upgrade and post-upgrade as thin wrappers around
the same prerm and postinst bodies the .ipk ships. One header line gives each
body the opkg upgrade contract it already handles: pre-upgrade rewrites apk's
"<new> <old>" arguments to "upgrade <new>", so prerm stops both services
without disabling them and leaves its marker, and post-upgrade exports
PKG_UPGRADE=1, as OpenWrt's own package-pack.mk does, so postinst starts fips
and starts the gateway only if it was enabled. Registering post-upgrade alone
would not have been enough: procd ignores a start of a running instance whose
command line is unchanged, so the services have to be stopped first.

testing/openwrt/package-test.sh runs the real build-apk.sh on the host
against a stub apk and checks the registered phases, the #! lines, that the
install and removal scripts are the shipped bodies, and, by executing each
wrapper's header with apk's argv and environment, that the upgrade pair hands
the bodies the right arguments. The ash harness runs it first and then runs
the captured scripts in three new apk scenarios, and the packaging workflow's
apk structural check now requires all four scripts in the adbdump.

An upgrade onto a package built this way was run on OpenWrt 25.12.2 with its
apk-tools 3.0.5: apk ran both pre-upgrade and post-upgrade, and both came from
the incoming package.

The adbdump key format the workflow check matches, each script as a
"<phase>:" key under scripts:, was read from the apk-tools v3.0.5 source
(src/serialize_yaml.c), the tag the packaging workflow builds from source.
It matches the dump that source-built 3.0.5 printed for this change in the
packaging workflow, where all four scripts appeared under scripts: and
passed the check on both architectures. OpenWrt's own apk-tools 3.0.5 is
built without mkpkg, and on a router adbdump cannot read the installed
database and info has no --scripts, so which scripts an installed package
registered cannot be read back on a device. The check covers the built
package only.
This commit is contained in:
Johnathan Corgan
2026-09-26 20:41:13 +00:00
parent 2ca7bd22f4
commit 9462d5d125
8 changed files with 449 additions and 10 deletions
+11
View File
@@ -841,6 +841,17 @@ jobs:
fi fi
done done
# Maintainer scripts. adbdump prints each registered script as a
# "<phase>:" key under scripts:. An upgrade runs only pre-upgrade and
# post-upgrade, so a package missing either restarts nothing.
for s in post-install pre-upgrade post-upgrade pre-deinstall; do
if grep -qE "^[[:space:]]*${s}:" "$DUMP"; then
echo " PASS script: $s"
else
echo " FAIL script: missing $s"; fail=1
fi
done
if [ "$fail" -ne 0 ]; then if [ "$fail" -ne 0 ]; then
echo "apk structural verification FAILED" echo "apk structural verification FAILED"
exit 1 exit 1
+15
View File
@@ -94,6 +94,21 @@ key; a single `--allow-untrusted` package install does not. If we ever publish a
apk feed, add ECDSA (prime256v1) signing via `apk mkpkg --sign` and distribute the apk feed, add ECDSA (prime256v1) signing via `apk mkpkg --sign` and distribute the
public key to `/etc/apk/keys/`. public key to `/etc/apk/keys/`.
## Upgrading
Upgrade with the same command, pointed at the new package:
```bash
ssh root@192.168.1.1 apk add --allow-untrusted /tmp/fips_<new-version>_<arch>.apk
```
The new package's upgrade scripts stop `fips` and `fips-gateway` before the
files are replaced, then start `fips` again and start `fips-gateway` only if it
was enabled, so the upgrade keeps the gateway's enabled state. apk runs
the incoming package's upgrade scripts, not the installed one's, so this holds
from the first upgrade onto a package that carries them, whatever version is
installed.
`/etc/fips/fips.yaml` is marked as a config file (via `/etc/fips/fips.yaml` is marked as a config file (via
`/lib/apk/packages/fips.conffiles`), so apk preserves local edits across upgrades, `/lib/apk/packages/fips.conffiles`), so apk preserves local edits across upgrades,
and `/lib/upgrade/keep.d/fips` preserves `/etc/fips/` across `sysupgrade` — the and `/lib/upgrade/keep.d/fips` preserves `/etc/fips/` across `sysupgrade` — the
+45 -6
View File
@@ -228,16 +228,53 @@ cat > "$STAGE_DIR/lib/apk/packages/${PKG_NAME}.conffiles" <<'EOF'
EOF EOF
# ---- maintainer scripts ---- # ---- maintainer scripts ----
# Map our opkg maintainer scripts onto apk's lifecycle phases:
# opkg postinst -> apk post-install (enable + start the daemon)
# opkg prerm -> apk pre-deinstall (stop + disable services)
# Both bodies come from packaging/openwrt-ipk/scripts/, the same files the # Both bodies come from packaging/openwrt-ipk/scripts/, the same files the
# .ipk ships, so the two packagers cannot drift apart and testing/openwrt/ # .ipk ships, so the two packagers cannot drift apart and testing/openwrt/
# exercises what both install. apk runs post-install only on a fresh install, # exercises what both install. apk-tools v3 runs a different script on each
# so the postinst's upgrade branch is unreachable here. # path, and only ever the incoming package's:
#
# fresh install post-install = postinst as shipped (enable + start fips;
# the gateway stays off)
# upgrade pre-upgrade = prerm, told it is an opkg-style upgrade;
# runs before any file is replaced
# post-upgrade = postinst with PKG_UPGRADE=1; runs after
# removal pre-deinstall = prerm as shipped (stop + disable both)
#
# On an upgrade apk passes "<new-version> <old-version>" and a PATH-only
# environment, which is not the contract the bodies were written for: prerm
# would read the new version as "not an upgrade" and disable the gateway, and
# postinst would see no PKG_UPGRADE. The upgrade pair therefore gets one header
# line that restores opkg's contract, "upgrade <new-version>" for prerm and
# PKG_UPGRADE=1 for postinst (OpenWrt's own package-pack.mk builds its
# post-upgrade scripts the same way).
#
# Registering post-upgrade alone would not restart anything: procd treats a
# start of a running instance with an unchanged command line as a no-op, so
# the old binaries would keep running until a reboot. pre-upgrade stops both
# services first, which also means a failed extraction leaves them stopped,
# as an opkg upgrade already does.
wrap_script() {
# wrap_script <header-line> <src> <dst>
# Writes <src> to <dst> with <header-line> inserted after its #! line.
local header="$1" src="$2" dst="$3"
if [ "$(head -n 1 "$src")" != "#!/bin/sh" ]; then
echo "Error: $src does not start with #!/bin/sh; cannot wrap it." >&2
exit 1
fi
{
echo "#!/bin/sh"
echo "$header"
tail -n +2 "$src"
} > "$dst"
chmod 0755 "$dst"
}
install -m 0755 "$SCRIPTS_SRC/postinst" "$SCRIPTS_DIR/post-install" install -m 0755 "$SCRIPTS_SRC/postinst" "$SCRIPTS_DIR/post-install"
install -m 0755 "$SCRIPTS_SRC/prerm" "$SCRIPTS_DIR/pre-deinstall" install -m 0755 "$SCRIPTS_SRC/prerm" "$SCRIPTS_DIR/pre-deinstall"
# shellcheck disable=SC2016 # $1 is expanded by the script at run time
wrap_script 'set -- upgrade "$1"' "$SCRIPTS_SRC/prerm" "$SCRIPTS_DIR/pre-upgrade"
wrap_script 'export PKG_UPGRADE=1' "$SCRIPTS_SRC/postinst" "$SCRIPTS_DIR/post-upgrade"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 3. Assemble the .apk via apk mkpkg # 3. Assemble the .apk via apk mkpkg
@@ -268,6 +305,8 @@ $FAKEROOT "$APK_BIN" mkpkg \
--info "maintainer:FIPS Network" \ --info "maintainer:FIPS Network" \
--info "depends:$DEPENDS" \ --info "depends:$DEPENDS" \
--script "post-install:$SCRIPTS_DIR/post-install" \ --script "post-install:$SCRIPTS_DIR/post-install" \
--script "pre-upgrade:$SCRIPTS_DIR/pre-upgrade" \
--script "post-upgrade:$SCRIPTS_DIR/post-upgrade" \
--script "pre-deinstall:$SCRIPTS_DIR/pre-deinstall" \ --script "pre-deinstall:$SCRIPTS_DIR/pre-deinstall" \
--files "$STAGE_DIR" \ --files "$STAGE_DIR" \
--output "$DIST_DIR/$PKG_FILENAME" --output "$DIST_DIR/$PKG_FILENAME"
+5 -3
View File
@@ -2,7 +2,8 @@
# Maintainer script run after the FIPS package is unpacked. # Maintainer script run after the FIPS package is unpacked.
# #
# Installed as the .ipk CONTROL/postinst and registered as the .apk # Installed as the .ipk CONTROL/postinst and registered as the .apk
# post-install script, so one body serves both packagers. # post-install script, and as the .apk post-upgrade script with PKG_UPGRADE=1
# exported ahead of this body, so one body serves both packagers.
# #
# The fips daemon is enabled and started on every install. The gateway is not: # The fips daemon is enabled and started on every install. The gateway is not:
# the package ships that service disabled, and the README and the deployment # the package ships that service disabled, and the README and the deployment
@@ -19,8 +20,9 @@
# re-enabled, which also re-enables one an operator had # re-enabled, which also re-enables one an operator had
# disabled by hand. # disabled by hand.
# #
# Under apk this script runs only on a fresh install, so it takes the first # Under apk, a fresh install runs this as post-install and the gateway stays
# branch and the gateway stays off. # off. An upgrade runs it as post-upgrade, after the .apk pre-upgrade script
# (the prerm body) has stopped the services and left the marker.
UPGRADE_MARKER=/tmp/fips-prerm-upgrade UPGRADE_MARKER=/tmp/fips-prerm-upgrade
+2 -1
View File
@@ -2,7 +2,8 @@
# Maintainer script run before the FIPS package is removed or replaced. # Maintainer script run before the FIPS package is removed or replaced.
# #
# Installed as the .ipk CONTROL/prerm and registered as the .apk pre-deinstall # Installed as the .ipk CONTROL/prerm and registered as the .apk pre-deinstall
# script, so one body serves both packagers. # script, and as the .apk pre-upgrade script with its arguments rewritten to
# "upgrade <new-version>", so one body serves both packagers.
# #
# opkg calls this with "upgrade <new-version>" when the package is being # opkg calls this with "upgrade <new-version>" when the package is being
# replaced. Disabling the services there would erase the operator's choice, # replaced. Disabling the services there would erase the operator's choice,
@@ -31,9 +31,23 @@ if [[ ! -f "$SCRIPT_DIR/scenarios.sh" ]]; then
exit 2 exit 2
fi fi
# The .apk wraps the shared bodies for its upgrade path. package-test.sh builds
# the package on the host with the real build-apk.sh, checks what it registers,
# and leaves the four scripts here so the scenarios run exactly what ships.
APK_DIR="$(mktemp -d)" || { echo "openwrt-scripts: mktemp failed" >&2; exit 2; }
trap 'rm -rf "$APK_DIR"' EXIT
bash "$SCRIPT_DIR/package-test.sh" --keep "$APK_DIR"
rc=$?
if [[ $rc -ne 0 ]]; then
echo "openwrt-scripts: package-test.sh exited $rc" >&2
exit $rc
fi
docker run --rm --network none \ docker run --rm --network none \
-v "$PROJECT_ROOT:/src:ro" \ -v "$PROJECT_ROOT:/src:ro" \
-v "$APK_DIR:/apk:ro" \
-e REPO=/src \ -e REPO=/src \
-e APK_SCRIPTS=/apk \
-e "POSTINST=${POSTINST:-}" \ -e "POSTINST=${POSTINST:-}" \
-e "PRERM=${PRERM:-}" \ -e "PRERM=${PRERM:-}" \
"$IMAGE" sh /src/testing/openwrt/scenarios.sh "$IMAGE" sh /src/testing/openwrt/scenarios.sh
+240
View File
@@ -0,0 +1,240 @@
#!/bin/bash
# ── OpenWrt package contents, checked on the host ───────────────────────────
# Runs the real packaging/openwrt-apk/build-apk.sh against placeholder
# binaries and a stub `apk` that records what `apk mkpkg` was asked to
# package. No docker, no apk-tools and no FIPS build are needed.
#
# What it checks is which maintainer scripts the .apk registers and what each
# one does with apk's upgrade arguments and environment (apk-tools v3 passes
# "<new-version> <old-version>" and a PATH-only environment to pre-upgrade and
# post-upgrade). Whether a real `apk mkpkg` accepts the result is left to the
# GitHub packaging workflow, which builds with the real tool.
#
# Usage: package-test.sh [--keep <dir>]
# --keep <dir> copy the captured apk scripts into <dir> as post-install,
# pre-upgrade, post-upgrade and pre-deinstall, so
# scenarios.sh can run them under ash.
#
# Exit 0 = every check passed. Exit 1 = at least one failed. Exit 2 = the
# harness could not run; never treated as a pass.
# ─────────────────────────────────────────────────────────────────────────────
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SCRIPTS_SRC="$PROJECT_ROOT/packaging/openwrt-ipk/scripts"
KEEP=""
while [[ $# -gt 0 ]]; do
case "$1" in
--keep)
[[ $# -ge 2 ]] || { echo "package-test: --keep needs a directory" >&2; exit 2; }
KEEP="$2"
shift 2
;;
*) echo "package-test: unknown argument: $1" >&2; exit 2 ;;
esac
done
if [[ -n "$KEEP" && ! -d "$KEEP" ]]; then
echo "package-test: --keep needs an existing directory" >&2
exit 2
fi
# The version is unique to this run so the cleanup below removes only what
# this run's builders wrote into dist/.
PKG_VERSION="pkgtest.$$"
TMP="$(mktemp -d)" || { echo "package-test: mktemp failed" >&2; exit 2; }
trap 'rm -rf "$TMP"; rm -f "$PROJECT_ROOT/dist/fips_${PKG_VERSION}_"*' EXIT
harness_fail() {
echo "package-test: $*" >&2
exit 2
}
FAILURES=0
CASES=0
ok() {
CASES=$((CASES + 1))
echo " ok $*"
return 0
}
bad() {
CASES=$((CASES + 1))
FAILURES=$((FAILURES + 1))
echo " FAIL $*"
return 0
}
# ── Build the .apk against a stub apk ───────────────────────────────────────
BINS="$TMP/bins"
CAPTURE="$TMP/capture"
mkdir -p "$BINS" "$CAPTURE" || harness_fail "cannot create $TMP subdirectories"
for bin in fips fipsctl fipstop fips-gateway; do
printf 'x' > "$BINS/$bin" || harness_fail "cannot write placeholder $bin"
done
# The stub knows only the arguments build-apk.sh passes today. Anything else
# exits 64, so a new mkpkg argument fails the build rather than going unseen.
cat > "$TMP/apk" <<STUB
#!/bin/bash
set -euo pipefail
cap='$CAPTURE'
[ "\${1-}" = mkpkg ] || { echo "stub apk: only mkpkg is supported, got '\${1-}'" >&2; exit 64; }
shift
files=""
out=""
while [ \$# -gt 0 ]; do
[ \$# -ge 2 ] || { echo "stub apk: \$1 has no value" >&2; exit 64; }
case "\$1" in
--info) ;;
--script)
phase="\${2%%:*}"
cp "\${2#*:}" "\$cap/script.\$phase"
echo "\$phase" >> "\$cap/phases"
;;
--files) files="\$2" ;;
--output) out="\$2" ;;
*) echo "stub apk: unknown argument \$1" >&2; exit 64 ;;
esac
shift 2
done
[ -n "\$files" ] && [ -n "\$out" ] || { echo "stub apk: --files and --output are required" >&2; exit 64; }
(cd "\$files" && find . -mindepth 1 | LC_ALL=C sort) > "\$cap/payload"
: > "\$out"
: > "\$cap/called"
STUB
chmod 0755 "$TMP/apk" || harness_fail "cannot make the stub apk executable"
echo "OpenWrt package checks"
echo "==> build-apk.sh with a stub apk"
if ! PKG_VERSION="$PKG_VERSION" APK_VERSION=0.0.0-r0 APK_BIN="$TMP/apk" \
bash "$PROJECT_ROOT/packaging/openwrt-apk/build-apk.sh" --arch x86_64 --bin-dir "$BINS" \
> "$TMP/build-apk.log" 2>&1; then
cat "$TMP/build-apk.log" >&2
harness_fail "build-apk.sh failed, so nothing was checked"
fi
[[ -f "$CAPTURE/called" ]] || harness_fail "build-apk.sh exited 0 but never ran apk mkpkg"
[[ -f "$CAPTURE/phases" ]] || harness_fail "apk mkpkg ran but no script phase was recorded"
# ── A1. The .apk registers exactly the four lifecycle scripts ───────────────
# apk runs only post-install on a fresh install, only pre-upgrade and
# post-upgrade on an upgrade, and only pre-deinstall on a removal.
WANT_PHASES="post-install post-upgrade pre-deinstall pre-upgrade"
got_phases="$(LC_ALL=C sort "$CAPTURE/phases" | tr '\n' ' ')"
got_phases="${got_phases% }"
if [[ "$got_phases" == "$WANT_PHASES" ]]; then
ok "A1 the .apk registers $WANT_PHASES"
else
missing=""
for phase in $WANT_PHASES; do
grep -qxF "$phase" "$CAPTURE/phases" || missing="$missing $phase"
done
bad "A1 registered phases are '$got_phases', want '$WANT_PHASES'; missing:${missing:- none}"
fi
# ── A2. Every registered script starts with a working #! line ───────────────
# apk execs the script directly, so the kernel reads line 1.
for phase in $WANT_PHASES; do
script="$CAPTURE/script.$phase"
if [[ ! -f "$script" ]]; then
bad "A2 $phase is not registered, so it has no #! line"
elif [[ "$(head -n 1 "$script")" == "#!/bin/sh" ]]; then
ok "A2 $phase starts with #!/bin/sh"
else
bad "A2 $phase starts with '$(head -n 1 "$script")', not #!/bin/sh"
fi
done
# ── A3. Install and removal ship the shared bodies unchanged ────────────────
for pair in post-install:postinst pre-deinstall:prerm; do
phase="${pair%%:*}"
src="$SCRIPTS_SRC/${pair#*:}"
if cmp -s "$CAPTURE/script.$phase" "$src"; then
ok "A3 $phase is the shipped ${pair#*:}, byte for byte"
else
bad "A3 $phase differs from the shipped ${pair#*:}"
fi
done
# ── A4 and A5. The upgrade pair wraps the shared bodies ─────────────────────
# A4 reads the structure: the script ends with the body after its #! line, and
# at least one header line sits between the two. A5 runs the header with apk's
# upgrade argv and environment, so what is judged is what the shell does with
# it, not how it reads.
for pair in pre-upgrade:prerm post-upgrade:postinst; do
phase="${pair%%:*}"
src="$SCRIPTS_SRC/${pair#*:}"
script="$CAPTURE/script.$phase"
if [[ ! -f "$script" ]]; then
bad "A4 $phase is not registered"
bad "A5 $phase is not registered, so its header cannot run"
continue
fi
tail -n +2 "$src" > "$TMP/body" || harness_fail "cannot read $src"
body_lines=$(wc -l < "$TMP/body")
script_lines=$(wc -l < "$script")
header_lines=$((script_lines - body_lines))
if [[ $header_lines -lt 2 ]]; then
bad "A4 $phase has $header_lines header line(s) before the ${pair#*:} body, want at least 2"
elif ! tail -n "$body_lines" "$script" | cmp -s - "$TMP/body"; then
bad "A4 $phase does not end with the ${pair#*:} body"
else
ok "A4 $phase is a $header_lines-line header and the ${pair#*:} body"
fi
if [[ $header_lines -lt 1 ]]; then
bad "A5 $phase has no header to run"
continue
fi
probe="$TMP/probe.$phase"
{
head -n "$header_lines" "$script"
# shellcheck disable=SC2016
printf '%s\n' 'printf '\''%s|%s|%s\n'\'' "${1-}" "${2-}" "${PKG_UPGRADE-}"'
} > "$probe"
chmod 0755 "$probe" || harness_fail "cannot make the $phase probe executable"
got="$(env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin "$probe" 0.6.0-r1 0.5.2-r1)"
rc=$?
if [[ $rc -eq 126 || $rc -eq 127 ]]; then
harness_fail "the $phase probe could not be executed (exit $rc)"
fi
IFS='|' read -r f1 f2 f3 <<< "$got"
case "$phase" in
pre-upgrade)
if [[ $rc -eq 0 && "$f1|$f2" == "upgrade|0.6.0-r1" ]]; then
ok "A5 pre-upgrade hands the body 'upgrade 0.6.0-r1'"
else
bad "A5 pre-upgrade hands the body '$f1 $f2' (exit $rc), want 'upgrade 0.6.0-r1'"
fi
;;
post-upgrade)
if [[ $rc -eq 0 && "$f3" == "1" ]]; then
ok "A5 post-upgrade runs the body with PKG_UPGRADE=1"
else
bad "A5 post-upgrade runs the body with PKG_UPGRADE='$f3' (exit $rc), want 1"
fi
;;
esac
done
# ── Hand the scripts to the ash scenarios ───────────────────────────────────
if [[ -n "$KEEP" ]]; then
for phase in $WANT_PHASES; do
[[ -f "$CAPTURE/script.$phase" ]] || continue
install -m 0755 "$CAPTURE/script.$phase" "$KEEP/$phase" \
|| harness_fail "cannot copy $phase into $KEEP"
done
fi
echo ""
if [[ $FAILURES -eq 0 ]]; then
echo "package-test: all $CASES checks passed"
exit 0
fi
echo "package-test: $FAILURES of $CASES checks failed"
exit 1
+117
View File
@@ -11,6 +11,13 @@
# POSTINST and PRERM may be pointed at other files. That is the seam used to # POSTINST and PRERM may be pointed at other files. That is the seam used to
# see a scenario red against the previously released scripts, and to re-break # see a scenario red against the previously released scripts, and to re-break
# the fixed ones during a break-check. # the fixed ones during a break-check.
#
# APK_SCRIPTS names a directory holding the four scripts the .apk registers
# (post-install, pre-upgrade, post-upgrade, pre-deinstall), as captured from
# the real build-apk.sh by package-test.sh --keep. Scenarios 8 to 10 execute
# them directly with apk-tools v3's argv and PATH-only environment, so the
# kernel reads their #! line and ash runs them. A missing directory or script
# fails those scenarios; it is never a skip.
set -u set -u
@@ -19,6 +26,7 @@ POSTINST="${POSTINST:-$REPO/packaging/openwrt-ipk/scripts/postinst}"
PRERM="${PRERM:-$REPO/packaging/openwrt-ipk/scripts/prerm}" PRERM="${PRERM:-$REPO/packaging/openwrt-ipk/scripts/prerm}"
RELEASED_PRERM="$REPO/testing/openwrt/fixtures/released-prerm" RELEASED_PRERM="$REPO/testing/openwrt/fixtures/released-prerm"
INIT_GATEWAY="$REPO/packaging/openwrt-ipk/files/etc/init.d/fips-gateway" INIT_GATEWAY="$REPO/packaging/openwrt-ipk/files/etc/init.d/fips-gateway"
APK_SCRIPTS="${APK_SCRIPTS:-}"
SHIPPED_YAML="$REPO/packaging/openwrt-ipk/files/etc/fips/fips.yaml" SHIPPED_YAML="$REPO/packaging/openwrt-ipk/files/etc/fips/fips.yaml"
WORK=/tmp/fips-openwrt-scenarios WORK=/tmp/fips-openwrt-scenarios
@@ -146,6 +154,52 @@ assert_absent() {
return 0 return 0
} }
assert_present() {
if [ -e "$1" ]; then
ok "$2"
else
bad "$2 — $1 does not exist"
fi
return 0
}
first_call_line() {
grep -nxF "$1" "$CALLS" | head -n 1 | cut -d: -f1
return 0
}
assert_order() {
# assert_order <first call> <second call> <what it means>
first="$(first_call_line "$1")"
second="$(first_call_line "$2")"
if [ -z "$first" ] || [ -z "$second" ]; then
bad "$3 — '$1' and '$2' were not both called: $(calls_oneline)"
elif [ "$first" -lt "$second" ]; then
ok "$3"
else
bad "$3 — '$2' came before '$1': $(calls_oneline)"
fi
return 0
}
run_apk_script() {
# run_apk_script <phase> <args...>
# Runs one captured .apk script the way apk-tools v3 does: executed
# directly, with only PATH in the environment. The stubs' own state
# variables are passed through so they can record the calls.
phase="$1"
shift
script="$APK_SCRIPTS/$phase"
if [ -z "$APK_SCRIPTS" ] || [ ! -x "$script" ]; then
bad "the .apk $phase script is not available at '$script'"
return 1
fi
env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin \
CALLS="$CALLS" GW_STATE="$GW_STATE" FIPS_STATE="$FIPS_STATE" \
"$script" "$@" >/dev/null 2>&1
return 0
}
# ── 1. Fresh install ──────────────────────────────────────────────────────── # ── 1. Fresh install ────────────────────────────────────────────────────────
# opkg runs the postinst with "configure"; PKG_UPGRADE is set only on upgrades, # opkg runs the postinst with "configure"; PKG_UPGRADE is set only on upgrades,
# so both its absence and an explicit 0 must leave the gateway alone. # so both its absence and an explicit 0 must leave the gateway alone.
@@ -321,9 +375,69 @@ YAML
return 0 return 0
} }
# ── 8. apk fresh install ────────────────────────────────────────────────────
# apk-tools v3 runs only post-install, with the new version as its argument.
scenario_apk_fresh_install() {
note "scenario 8: apk fresh install"
reset_state
run_apk_script post-install 0.6.0-r1 || return 0
assert_called "fips enable" "an apk install enables the daemon"
assert_called "fips start" "an apk install starts the daemon"
assert_not_called "fips-gateway enable" "an apk install does not enable the gateway"
assert_not_called "fips-gateway start" "an apk install does not start the gateway"
assert_file_is "$GW_STATE" "0" "an apk install leaves the gateway disabled"
return 0
}
# ── 9. apk upgrade, gateway enabled ─────────────────────────────────────────
# apk-tools v3 runs only the new package's pre-upgrade and post-upgrade, with
# "<new-version> <old-version>"; the old package runs nothing.
scenario_apk_upgrade_enabled() {
note "scenario 9: apk upgrade, gateway enabled"
reset_state
echo 1 > "$GW_STATE"
echo 1 > "$FIPS_STATE"
run_apk_script pre-upgrade 0.6.0-r1 0.5.2-r1 || return 0
assert_called "fips-gateway stop" "pre-upgrade stops the gateway"
assert_called "fips stop" "pre-upgrade stops the daemon"
assert_not_called "fips-gateway disable" "pre-upgrade does not disable the gateway"
assert_not_called "fips disable" "pre-upgrade does not disable the daemon"
assert_file_is "$GW_STATE" "1" "the gateway is still enabled after pre-upgrade"
assert_present "$UPGRADE_MARKER" "pre-upgrade leaves the upgrade marker"
run_apk_script post-upgrade 0.6.0-r1 0.5.2-r1 || return 0
assert_order "fips stop" "fips start" "the daemon is started again after it was stopped"
assert_order "fips-gateway stop" "fips-gateway start" "the gateway is started again after it was stopped"
assert_not_called "fips-gateway enable" "an enabled gateway does not need re-enabling"
assert_file_is "$GW_STATE" "1" "the gateway stays enabled across the apk upgrade"
assert_absent "$UPGRADE_MARKER" "post-upgrade removes the upgrade marker"
return 0
}
# ── 10. apk upgrade, gateway disabled ───────────────────────────────────────
scenario_apk_upgrade_disabled() {
note "scenario 10: apk upgrade, gateway disabled"
reset_state
echo 1 > "$FIPS_STATE"
run_apk_script pre-upgrade 0.6.0-r1 0.5.2-r1 || return 0
run_apk_script post-upgrade 0.6.0-r1 0.5.2-r1 || return 0
assert_order "fips stop" "fips start" "the daemon is started again after it was stopped"
assert_file_is "$GW_STATE" "0" "a disabled gateway stays disabled across the apk upgrade"
assert_not_called "fips-gateway enable" "a disabled gateway is not enabled by the apk upgrade"
assert_not_called "fips-gateway start" "a disabled gateway is not started by the apk upgrade"
assert_absent "$UPGRADE_MARKER" "post-upgrade removes the upgrade marker"
return 0
}
echo "OpenWrt maintainer-script scenarios (shell: $(readlink -f /proc/$$/exe 2>/dev/null || echo sh))" echo "OpenWrt maintainer-script scenarios (shell: $(readlink -f /proc/$$/exe 2>/dev/null || echo sh))"
echo " postinst: $POSTINST" echo " postinst: $POSTINST"
echo " prerm: $PRERM" echo " prerm: $PRERM"
echo " apk: ${APK_SCRIPTS:-(not set)}"
scenario_fresh_install scenario_fresh_install
scenario_upgrade_from_released scenario_upgrade_from_released
@@ -332,6 +446,9 @@ scenario_upgrade_disabled
scenario_removal scenario_removal
scenario_config_reader scenario_config_reader
scenario_start_service_guard scenario_start_service_guard
scenario_apk_fresh_install
scenario_apk_upgrade_enabled
scenario_apk_upgrade_disabled
echo "" echo ""
if [ "$FAILURES" -eq 0 ]; then if [ "$FAILURES" -eq 0 ]; then