From a4ee69dd6b9f1564a5f89780e5795892b931e864 Mon Sep 17 00:00:00 2001 From: Kevin Stenzel Date: Mon, 28 Sep 2026 15:56:45 +0200 Subject: [PATCH 1/5] fix(debian): upgrades keep a service the admin disabled Up to 7.3.6 every configure ran `systemctl enable` and started the service, so an admin's `systemctl disable --now` was undone by the next upgrade. The maintainer scripts now follow Debian's rules the way the dh_installsystemd snippets do, still by hand because the unit stays in /lib/systemd/system: * postinst: deb-systemd-helper unmask, then enable when was-enabled, update-state otherwise (links are made without a running systemd too). With systemd running: an enabled unit is restarted on upgrade (started on a new installation) with deb-systemd-invoke; a disabled or masked one only gets try-restart (a running one loads the new code, a stopped one stays stopped). The instance update runs only when the service runs after the configure, otherwise apt shows one line saying so. * preinst (new): one-time transition. 7.3.6 and older never recorded deb-systemd-helper state, and without it was-enabled says "enabled" even for a disabled unit. On an upgrade without recorded state the current state is recorded (enabled -> enable, otherwise update-state; the latter only while systemd runs: without it the old postinst never enabled the unit). A reinstall from config-files is not an upgrade. * prerm remove: deb-systemd-invoke stop only (no disable). * postrm remove: deb-systemd-helper mask; purge: purge + unmask. Measured in a systemd container (ubuntu:24.04, init-system-helpers 1.66ubuntu1): deb-systemd-invoke restart of a masked running unit fails and of a disabled stopped one prints "not starting it", try-restart is silent in both, so only enabled units get restart/start. debian/changelog: 7.3.7 (lmn73). --- RELEASE.md | 3 ++- debian/changelog | 33 +++++++++++++++++++++++++++++ debian/postinst | 48 ++++++++++++++++++++++++++++++++----------- debian/postrm | 10 +++++++++ debian/preinst | 27 ++++++++++++++++++++++++ debian/prerm | 10 +++++---- debian/rules | 3 ++- docs/install.md | 10 ++++++--- docs/operations.md | 47 +++++++++++++++++++++++++++++++++++------- docs/test-strategy.md | 4 +++- 10 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 debian/preinst diff --git a/RELEASE.md b/RELEASE.md index 75f6ba6..1e143cb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -52,7 +52,8 @@ does not yet exist — every `lmnsquid create --image ghcr.io/…@sha256: Mon, 28 Sep 2026 15:29:04 +0200 + linuxmuster-squid (7.3.6) lmn73; urgency=medium * A git step of the instance change log that fails during the package diff --git a/debian/postinst b/debian/postinst index a682ca4..18dac8e 100644 --- a/debian/postinst +++ b/debian/postinst @@ -113,21 +113,45 @@ fi # blanket 0755), so anything intentionally restrictive stays as restrictive as it was. chmod -R go-w /opt/linuxmuster-squid +# The service by Debian's rules, as dh_installsystemd's snippets do it, by hand because the +# unit stays in /lib/systemd/system (moving it to /usr/lib within one package risks losing it +# under usrmerge). deb-systemd-helper keeps the admin's decision: a new installation enables +# the service, afterwards it stays as the admin left it, so `systemctl disable` survives +# upgrades (up to 7.3.6 every configure enabled it again). The preinst recorded the state of a +# server coming from 7.3.6 or older. Without a running systemd (image build, chroot) only the +# links are made and the service starts with the next boot. +UNIT=linuxmuster-squid.service +deb-systemd-helper unmask "$UNIT" >/dev/null || true +if deb-systemd-helper --quiet was-enabled "$UNIT"; then + deb-systemd-helper enable "$UNIT" >/dev/null || true +else + deb-systemd-helper update-state "$UNIT" >/dev/null || true +fi + if [ -d /run/systemd/system ]; then systemctl daemon-reload || true - systemctl enable linuxmuster-squid.service || true - # Load new code on a genuine upgrade (try-restart no-ops if the service is not - # running), then start unconditionally so it is up in ALL cases -- fresh install - # AND reinstall over a removed/config-files state, where try-restart alone would - # leave it stopped. `start` is a no-op if it is already running. - if [ -n "${2:-}" ]; then - systemctl try-restart linuxmuster-squid.service || true + # Enabled: restart on an upgrade (loads the new code, starts a stopped service), start on + # a new installation. Disabled or masked: only try-restart, so a service that still runs + # gets the new code and a stopped one stays stopped (restart would do the same for a + # disabled unit but print "not starting it", and it fails on a masked unit that runs). + # deb-systemd-invoke honours policy-rc.d. + if [ "$(systemctl is-enabled "$UNIT" 2>/dev/null)" = enabled ]; then + if [ -n "${2:-}" ]; then + deb-systemd-invoke restart "$UNIT" || true + else + deb-systemd-invoke start "$UNIT" || true + fi + else + deb-systemd-invoke try-restart "$UNIT" || true fi - systemctl start linuxmuster-squid.service || true - # On upgrade, lift all running instances onto this package's pinned default - # image (best-effort; each instance updates with its own health-check - # auto-rollback). Wait briefly for the restarted API; never fail the install. - if [ -n "${2:-}" ]; then + # On upgrade, lift all instances onto this package's pinned default image (best-effort; + # each instance updates with its own health-check auto-rollback), but only through a + # service that runs after this configure; a service the admin keeps off is not started + # for it. Wait briefly for the restarted API; never fail the install. + if [ -n "${2:-}" ] && ! systemctl is-active --quiet "$UNIT"; then + echo "linuxmuster-squid: the service is $(systemctl is-enabled "$UNIT" 2>/dev/null)" \ + "and not running, instances not updated; run 'lmnsquid update-all' after starting it" + elif [ -n "${2:-}" ]; then LMN=/opt/linuxmuster-squid/venv/bin/lmnsquid i=0 while [ "$i" -lt 30 ]; do diff --git a/debian/postrm b/debian/postrm index 88d3bd8..f9987dd 100644 --- a/debian/postrm +++ b/debian/postrm @@ -5,8 +5,18 @@ set -e if [ -d /run/systemd/system ]; then systemctl daemon-reload || true fi +# The admin's enable/disable decision survives a remove (the unit stays masked until the +# postinst of a reinstall unmasks it) and is forgotten on purge; Debian's rule, as +# dh_installsystemd does it. +if [ "$1" = remove ] && [ -x /usr/bin/deb-systemd-helper ]; then + deb-systemd-helper mask linuxmuster-squid.service >/dev/null || true +fi # Deliberately do NOT delete config/state + venv on remove (only on purge). if [ "$1" = "purge" ]; then + if [ -x /usr/bin/deb-systemd-helper ]; then + deb-systemd-helper purge linuxmuster-squid.service >/dev/null || true + deb-systemd-helper unmask linuxmuster-squid.service >/dev/null || true + fi rm -rf /opt/linuxmuster-squid rm -rf /etc/linuxmuster-squid /var/lib/linuxmuster-squid # The safe.directory entry the postinst added for the instances repo. diff --git a/debian/preinst b/debian/preinst new file mode 100644 index 0000000..d976e39 --- /dev/null +++ b/debian/preinst @@ -0,0 +1,27 @@ +#!/bin/sh +# SPDX-FileCopyrightText: Kevin Stenzel +# SPDX-License-Identifier: GPL-3.0-or-later +set -e + +# One-time transition to Debian's service rules (7.3.7). Up to 7.3.6 the maintainer scripts +# enabled the service by hand and never recorded deb-systemd-helper state, and without that +# state `deb-systemd-helper was-enabled` answers "enabled" even for a service the admin +# disabled, so the postinst would enable it again. On an upgrade of the installed package the +# old unit and the admin's choice are still in place: record them now, before anything +# changes. Enabled -> enable (records the existing link), disabled or masked -> update-state +# (known, not enabled). Not while systemd is down (an image build, a chroot): the old postinst +# enabled the service only while systemd ran, so a missing link there is no admin decision, +# and the postinst enables it like a new installation. A reinstall from the config-files state +# ("install ") is left alone for the same reason: the old prerm disabled the service +# on remove, the admin did not. +UNIT=linuxmuster-squid.service +if [ "$1" = upgrade ] && ! deb-systemd-helper debian-installed "$UNIT"; then + if [ "$(systemctl is-enabled "$UNIT" 2>/dev/null)" = enabled ]; then + deb-systemd-helper enable "$UNIT" >/dev/null || true + elif [ -d /run/systemd/system ]; then + deb-systemd-helper update-state "$UNIT" >/dev/null || true + fi +fi + +#DEBHELPER# +exit 0 diff --git a/debian/prerm b/debian/prerm index 11ba201..2e19fbc 100644 --- a/debian/prerm +++ b/debian/prerm @@ -2,11 +2,13 @@ # SPDX-FileCopyrightText: Kevin Stenzel # SPDX-License-Identifier: GPL-3.0-or-later set -e -# Stop/disable ONLY on a real remove. On upgrade ($1=upgrade), keep the service -# RUNNING so postinst can restart it via try-restart (new code); stopping it here -# would leave it inactive after the upgrade. +# Stop ONLY on a real remove. On upgrade ($1=upgrade), keep the service RUNNING so the +# postinst restarts it (new code); stopping it here would leave it inactive after the +# upgrade. Whether it is enabled stays the admin's decision (Debian's rule, as +# dh_installsystemd does it): the postrm masks the unit while the package is removed, a +# reinstall restores the decision, a purge forgets it. Up to 7.3.6 remove also disabled it. if [ "$1" = remove ] && [ -d /run/systemd/system ]; then - systemctl disable --now linuxmuster-squid.service || true + deb-systemd-invoke stop linuxmuster-squid.service >/dev/null || true fi #DEBHELPER# exit 0 diff --git a/debian/rules b/debian/rules index d6a3259..fff552f 100755 --- a/debian/rules +++ b/debian/rules @@ -40,7 +40,8 @@ override_dh_auto_clean: override_dh_strip override_dh_dwz override_dh_strip_nondeterminism override_dh_makeshlibs: @: -# The maintainer scripts handle the units by hand, unchanged since 7.3.x (the plan, §4). +# The maintainer scripts handle the unit by hand, with deb-systemd-helper and deb-systemd-invoke +# as dh_installsystemd's snippets do, because the unit stays in /lib/systemd/system. override_dh_installsystemd: @: diff --git a/docs/install.md b/docs/install.md index 4377da8..cb335c9 100644 --- a/docs/install.md +++ b/docs/install.md @@ -46,8 +46,8 @@ lmnsquid version # {"version": "7.3.6"} (= dpkg-query -W linuxmuster-sq The postinst creates the system user `lmnsquid` (in group `docker`), the config `/etc/linuxmuster-squid/config.yml` with a random API token (0600), `secrets/` (0700) and `blocklists/`, the git repository `/var/lib/linuxmuster-squid/instances` (the change log of -the instance definitions), and starts the service on `127.0.0.1:8080`. The admin scripts used -below are installed under `/usr/share/linuxmuster-squid/scripts/`. +the instance definitions), and enables and starts the service on `127.0.0.1:8080`. The admin +scripts used below are installed under `/usr/share/linuxmuster-squid/scripts/`. ## 3. Service account and keytab (on the DC) @@ -164,10 +164,14 @@ Assign the proxy per role by GPO (silent Kerberos SSO) and force the proxy at th ## 9. Updates ```bash -apt-get install -y ./linuxmuster-squid__amd64.deb # restarts the service, then update-all +apt-get install -y ./linuxmuster-squid__amd64.deb # restarts the service, then update-all (*) lmnsquid update-all # on demand: every instance -> pinned default image ``` +(*) Only while the service is enabled or still running: a service switched off with +`systemctl disable --now` stays off, and the instances are not updated +([operations.md](operations.md#keeping-the-service-off)). + Every instance update is health-gated with automatic rollback: the replacement container is created first, the previous one is kept until the new one is **healthy** and restarted if it is not. **Upgrading from 7.3.0:** containers created with 7.3.0 run without the blocklist diff --git a/docs/operations.md b/docs/operations.md index 515705e..866e8b9 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -16,8 +16,9 @@ apt install ./linuxmuster-squid__amd64.deb # or from the lmn73 apt re systemctl status linuxmuster-squid # should be "active" ``` The postinst creates the system user `lmnsquid` (in group `docker`), generates a -random API token in `/etc/linuxmuster-squid/config.yml` (0600) and starts the service -(bound to `127.0.0.1:8080`). +random API token in `/etc/linuxmuster-squid/config.yml` (0600) and enables and starts the +service (bound to `127.0.0.1:8080`). After that the package keeps the admin's decision: +[Keeping the service off](#keeping-the-service-off). ## Managing instances (CLI = thin client of the REST API) @@ -105,10 +106,12 @@ The update pulls the new digest, replaces the container, waits for `healthy` and belongs in production is decided by a **merged PR** (never auto-merge; raised by hand while Renovate is disabled). -On a **`.deb` upgrade** the postinst runs `update-all` automatically (best-effort): all -instances are lifted onto that package's pinned default image, each with its own health-check -auto-rollback; instances already on the default are skipped, and the apt transaction never -fails over this. Run `lmnsquid update-all` yourself any time to do the same on demand. +On a **`.deb` upgrade** the postinst runs `update-all` automatically (best-effort) when the +service runs after the upgrade: all instances are lifted onto that package's pinned default +image, each with its own health-check auto-rollback; instances already on the default are +skipped, and the apt transaction never fails over this. A service the admin switched off is not +started for it ([Keeping the service off](#keeping-the-service-off)). Run `lmnsquid update-all` +yourself any time to do the same on demand. **Upgrade from 7.3.0 (or older) to 7.3.1:** instances created before 7.3.1 have no blocklist mount, and `update-all` skips them when the default image did not move. The postinst therefore @@ -130,6 +133,35 @@ instances are still reconciled). A container that already matches its definition `lmnsquid.spec`) is left running — so `lmnsquid reconcile` is safe to run at any time and an `edit` that changes nothing causes no restart. Use `lmnsquid restart ` for a plain restart. +## Keeping the service off + +From 7.3.7 on the package handles its service by Debian's rules (`deb-systemd-helper` and +`deb-systemd-invoke`, as packages built with dh_installsystemd do): a new installation enables +and starts `linuxmuster-squid`; after that, upgrades keep what the admin decided. + +- **Switch it off and keep it off:** `systemctl disable --now linuxmuster-squid`. Upgrades, + reinstalls and `dpkg-reconfigure` leave it disabled and stopped, do not update the instances + and print one line in the apt output: `linuxmuster-squid: the service is disabled and not + running, instances not updated; run 'lmnsquid update-all' after starting it`. + `systemctl stop` alone is not enough: the next upgrade starts an enabled service again. +- **The proxies are separate:** the Squid containers are Docker containers + (`unless-stopped`) and keep running without the control plane. If they must be off too, stop + them first with `lmnsquid stop ` (it needs the running service). +- **Switch it on again:** `systemctl enable --now linuxmuster-squid`, then `lmnsquid update-all` + for the instance updates the upgrades skipped. +- **Disabled but still running** (`systemctl disable` without `--now`): an upgrade restarts it + with the new code and updates the instances; it stays disabled. +- **Masked** (`systemctl mask`): the package leaves it alone. +- **Remove and reinstall:** `apt remove` only stops the service and keeps the decision (the unit + stays masked while the package is removed); a reinstall brings it back as it was. `apt purge` + forgets it: the next installation is a new one, enabled and started. A reinstall after removing + 7.3.6 or older is a new installation as well (those versions disabled the service on remove). +- **The first upgrade to 7.3.7** takes over the state the service is in at that moment, once: + 7.3.6 and older kept no record of it. Enabled stays enabled, disabled or masked stays so. +- **Downgrade to 7.3.6 or older:** that version enables and starts the service again. +- **Without a running systemd** (an image build, a chroot) a new installation only enables the + service; it starts with the next boot. + ## Observing ``` @@ -233,7 +265,8 @@ lmnsquid reconcile # reads the desired state + pulls the pinned digests -> healthy); one failing instance does not stop the rest (`failed` list, CLI exit 1). - **Reboot** doesn't need this: `restart_policy: unless-stopped` brings running containers back. - **Downgrade the tool:** install an older `.deb` → the postinst restarts the service - (loads the old code). **Cache volume broken** (container stays unhealthy after a power outage): + (loads the old code); 7.3.6 and older also enable it again. **Cache volume broken** + (container stays unhealthy after a power outage): `docker rm -f ` + `docker volume rm lmnsquid-cache-` → `lmnsquid reconcile`. ## Security posture (brief) diff --git a/docs/test-strategy.md b/docs/test-strategy.md index 83ef363..cf9938c 100644 --- a/docs/test-strategy.md +++ b/docs/test-strategy.md @@ -115,7 +115,9 @@ acceptance list in `deployment-gpo.md`. failed step and the repository, the package stays `ii` and the service as enabled/active as before, and once `HEAD` is back the next configure commits again without a warning; upgrade over a release with root-owned files in the change log repository → handed - back to `lmnsquid`, history kept. + back to `lmnsquid`, history kept; a service the admin disabled (`systemctl disable + --now`) before the upgrade from the release stays disabled and stopped across two upgrades + in a row, with the "instances not updated" line in the apt output (CI upgrade-smoke). - **P10:** keytab perms; manager ACL not reachable externally; API bind ≠ 0.0.0.0; bypass/traversal; DC outage does not stall (ttl/grace). From bd1e1996f3d7a14b5d6b69f65b7fa6328212d2ea Mon Sep 17 00:00:00 2001 From: Kevin Stenzel Date: Mon, 28 Sep 2026 15:56:45 +0200 Subject: [PATCH 2/5] test(ci): upgrade-smoke keeps a service the admin disabled A permanent case in upgrade-smoke (ci.yml and release.yml, same step in both, no new job, so the required checks stay as they are): purge, install the published release, `systemctl disable --now`, then upgrade to the built package twice. After each upgrade the unit is disabled and inactive, the journal shows no start since the upgrade began, and apt printed the "instances not updated" line; afterwards deb-systemd-helper holds the recorded state. The fast tier shellchecks debian/preinst too. --- .github/workflows/ci.yml | 42 +++++++++++++++++++++++++++++++++-- .github/workflows/release.yml | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8be2044..b45f3a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,7 +105,7 @@ jobs: - name: pytest run: pytest -q controlplane/tests - name: shellcheck - run: sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck && shellcheck --severity=warning $(git ls-files '*.sh') debian/postinst debian/prerm debian/postrm + run: sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck && shellcheck --severity=warning $(git ls-files '*.sh') debian/preinst debian/postinst debian/prerm debian/postrm - name: reuse (license compliance) run: reuse lint - name: blocklist smoke @@ -540,7 +540,7 @@ jobs: # --reinstall --allow-downgrades: on main the build is regularly version-equal # to the release, and a plain install would say "already the newest version" # and test nothing. dpkg must run the postinst with $2 set, or its upgrade - # branch (try-restart, update-all, reconcile) never runs. + # branch (restart, update-all, reconcile) never runs. sudo apt-get install -y --reinstall --allow-downgrades ./packages/*.deb 2>&1 \ | tee upgrade.log @@ -645,6 +645,44 @@ jobs: echo '```' } >> "$GITHUB_STEP_SUMMARY" + # Debian's rule from 7.3.7 on: an upgrade keeps a service the admin switched off. Once + # more from the published release, which never recorded deb-systemd-helper state (so this + # is the preinst's one-time transition), then a second time on top: the recorded state + # must hold. + # The disabled service is neither started for a moment nor used for the instance update, and + # apt shows the one line that says so. + - name: A service disabled before the upgrade stays disabled and stopped + if: steps.prev.outputs.skip != '1' + run: | + set -eux + set -o pipefail + P=linuxmuster-squid + U=$P.service + STATE=/var/lib/systemd/deb-systemd-helper-enabled/$U.dsh-also + sudo apt-get purge -y linuxmuster-squid + test ! -e "$STATE" # purge forgot the state: the release starts from scratch + sudo apt-get install -y ./previous/*.deb + test "$(systemctl is-enabled $U)" = enabled + sudo systemctl disable --now $U + test "$(systemctl is-active $U || true)" = inactive + for pass in 1 2; do + cursor="$(sudo journalctl -q -n 0 --show-cursor | sed -n 's/^-- cursor: //p')" + test -n "$cursor" + sudo apt-get install -y --reinstall --allow-downgrades ./packages/*.deb 2>&1 \ + | tee "disabled-$pass.log" + grep -E 'Unpacking linuxmuster-squid \([^)]+\) over \([^)]+\)' "disabled-$pass.log" + test "$(systemctl is-enabled $U || true)" = disabled + test "$(systemctl is-active $U || true)" = inactive + test "$(sudo journalctl -q --no-pager --after-cursor="$cursor" -u $U \ + | grep -c -F 'Started ' || true)" = 0 + grep -F "$P: the service is disabled and not running, instances not updated" \ + "disabled-$pass.log" + done + test -e "$STATE" # the transition recorded the admin's decision + test -z "$(dpkg --audit)" + echo "### disabled before the upgrade: disabled and stopped after two upgrades" \ + >> "$GITHUB_STEP_SUMMARY" + - name: Journal of the control plane (always, for diagnosis) if: always() && steps.prev.outputs.skip != '1' run: sudo journalctl -u linuxmuster-squid.service --no-pager | tail -60 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd03df2..295dd10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -451,7 +451,7 @@ jobs: # --reinstall --allow-downgrades: on main the build is regularly version-equal # to the release, and a plain install would say "already the newest version" # and test nothing. dpkg must run the postinst with $2 set, or its upgrade - # branch (try-restart, update-all, reconcile) never runs. + # branch (restart, update-all, reconcile) never runs. sudo apt-get install -y --reinstall --allow-downgrades ./packages/*.deb 2>&1 \ | tee upgrade.log @@ -556,6 +556,44 @@ jobs: echo '```' } >> "$GITHUB_STEP_SUMMARY" + # Debian's rule from 7.3.7 on: an upgrade keeps a service the admin switched off. Once + # more from the published release, which never recorded deb-systemd-helper state (so this + # is the preinst's one-time transition), then a second time on top: the recorded state + # must hold. + # The disabled service is neither started for a moment nor used for the instance update, and + # apt shows the one line that says so. + - name: A service disabled before the upgrade stays disabled and stopped + if: steps.prev.outputs.skip != '1' + run: | + set -eux + set -o pipefail + P=linuxmuster-squid + U=$P.service + STATE=/var/lib/systemd/deb-systemd-helper-enabled/$U.dsh-also + sudo apt-get purge -y linuxmuster-squid + test ! -e "$STATE" # purge forgot the state: the release starts from scratch + sudo apt-get install -y ./previous/*.deb + test "$(systemctl is-enabled $U)" = enabled + sudo systemctl disable --now $U + test "$(systemctl is-active $U || true)" = inactive + for pass in 1 2; do + cursor="$(sudo journalctl -q -n 0 --show-cursor | sed -n 's/^-- cursor: //p')" + test -n "$cursor" + sudo apt-get install -y --reinstall --allow-downgrades ./packages/*.deb 2>&1 \ + | tee "disabled-$pass.log" + grep -E 'Unpacking linuxmuster-squid \([^)]+\) over \([^)]+\)' "disabled-$pass.log" + test "$(systemctl is-enabled $U || true)" = disabled + test "$(systemctl is-active $U || true)" = inactive + test "$(sudo journalctl -q --no-pager --after-cursor="$cursor" -u $U \ + | grep -c -F 'Started ' || true)" = 0 + grep -F "$P: the service is disabled and not running, instances not updated" \ + "disabled-$pass.log" + done + test -e "$STATE" # the transition recorded the admin's decision + test -z "$(dpkg --audit)" + echo "### disabled before the upgrade: disabled and stopped after two upgrades" \ + >> "$GITHUB_STEP_SUMMARY" + - name: Journal of the control plane (always, for diagnosis) if: always() && steps.prev.outputs.skip != '1' run: sudo journalctl -u linuxmuster-squid.service --no-pager | tail -60 From a0d8013c417371e75c8b6becaae61d968b1061b2 Mon Sep 17 00:00:00 2001 From: Kevin Stenzel Date: Mon, 28 Sep 2026 16:29:23 +0200 Subject: [PATCH 3/5] fix(debian): postrm calls deb-systemd-helper without an -x guard lintian warned command-with-path-in-maintainer-script for the `[ -x /usr/bin/deb-systemd-helper ]` tests (dh_installsystemd's own snippets are exempt as generated code). init-system-helpers is Essential on Ubuntu 24.04, and `|| true` already covers a missing command. --- debian/postrm | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/debian/postrm b/debian/postrm index f9987dd..2bbdc6a 100644 --- a/debian/postrm +++ b/debian/postrm @@ -6,17 +6,15 @@ if [ -d /run/systemd/system ]; then systemctl daemon-reload || true fi # The admin's enable/disable decision survives a remove (the unit stays masked until the -# postinst of a reinstall unmasks it) and is forgotten on purge; Debian's rule, as -# dh_installsystemd does it. -if [ "$1" = remove ] && [ -x /usr/bin/deb-systemd-helper ]; then +# postinst of a reinstall unmasks it) and is forgotten on purge. deb-systemd-helper comes +# with init-system-helpers, which is Essential. +if [ "$1" = remove ]; then deb-systemd-helper mask linuxmuster-squid.service >/dev/null || true fi # Deliberately do NOT delete config/state + venv on remove (only on purge). if [ "$1" = "purge" ]; then - if [ -x /usr/bin/deb-systemd-helper ]; then - deb-systemd-helper purge linuxmuster-squid.service >/dev/null || true - deb-systemd-helper unmask linuxmuster-squid.service >/dev/null || true - fi + deb-systemd-helper purge linuxmuster-squid.service >/dev/null || true + deb-systemd-helper unmask linuxmuster-squid.service >/dev/null || true rm -rf /opt/linuxmuster-squid rm -rf /etc/linuxmuster-squid /var/lib/linuxmuster-squid # The safe.directory entry the postinst added for the instances repo. From 3a0176970d588eb84fe80622d5f010c4d6988da0 Mon Sep 17 00:00:00 2001 From: Kevin Stenzel Date: Mon, 28 Sep 2026 16:44:59 +0200 Subject: [PATCH 4/5] fix(debian): remove/purge as debhelper 13.14's dh_installsystemd snippets debhelper 13.10 stopped masking system units on package removal (Debian #1021027: the mask only kept a left-over sysv init script from starting, and these packages ship none), and its purge snippet runs only `deb-systemd-helper purge`. Ubuntu 24.04 has 13.14, so the postrm no longer masks on remove and no longer unmasks on purge. What the 13.14 snippets still do stays: postinst `deb-systemd-helper unmask` (lifts only a mask deb-systemd-helper set itself) followed by enable/update-state, prerm `deb-systemd-invoke stop` on remove only, postrm `deb-systemd-helper purge` on purge (the daemon-reload stays as before). The decision still survives remove + reinstall: the prerm only stops the service and the enable link and the recorded state stay until purge. docs/operations.md no longer says the unit stays masked while removed. --- debian/postinst | 2 ++ debian/postrm | 11 ++++------- debian/prerm | 4 ++-- docs/operations.md | 8 ++++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/debian/postinst b/debian/postinst index 18dac8e..08077ce 100644 --- a/debian/postinst +++ b/debian/postinst @@ -121,6 +121,8 @@ chmod -R go-w /opt/linuxmuster-squid # server coming from 7.3.6 or older. Without a running systemd (image build, chroot) only the # links are made and the service starts with the next boot. UNIT=linuxmuster-squid.service +# unmask as dh_installsystemd still does: it lifts only a mask deb-systemd-helper set itself +# (older debhelper masked on remove), never one of the admin. deb-systemd-helper unmask "$UNIT" >/dev/null || true if deb-systemd-helper --quiet was-enabled "$UNIT"; then deb-systemd-helper enable "$UNIT" >/dev/null || true diff --git a/debian/postrm b/debian/postrm index 2bbdc6a..dfef95b 100644 --- a/debian/postrm +++ b/debian/postrm @@ -5,16 +5,13 @@ set -e if [ -d /run/systemd/system ]; then systemctl daemon-reload || true fi -# The admin's enable/disable decision survives a remove (the unit stays masked until the -# postinst of a reinstall unmasks it) and is forgotten on purge. deb-systemd-helper comes -# with init-system-helpers, which is Essential. -if [ "$1" = remove ]; then - deb-systemd-helper mask linuxmuster-squid.service >/dev/null || true -fi # Deliberately do NOT delete config/state + venv on remove (only on purge). if [ "$1" = "purge" ]; then + # The admin's enable/disable decision survives a remove (the prerm only stops the service) + # and is forgotten here, as debhelper 13.14's dh_installsystemd snippet does it (no mask on + # remove, no unmask on purge since debhelper 13.10). deb-systemd-helper comes with + # init-system-helpers, which is Essential. deb-systemd-helper purge linuxmuster-squid.service >/dev/null || true - deb-systemd-helper unmask linuxmuster-squid.service >/dev/null || true rm -rf /opt/linuxmuster-squid rm -rf /etc/linuxmuster-squid /var/lib/linuxmuster-squid # The safe.directory entry the postinst added for the instances repo. diff --git a/debian/prerm b/debian/prerm index 2e19fbc..2f92977 100644 --- a/debian/prerm +++ b/debian/prerm @@ -5,8 +5,8 @@ set -e # Stop ONLY on a real remove. On upgrade ($1=upgrade), keep the service RUNNING so the # postinst restarts it (new code); stopping it here would leave it inactive after the # upgrade. Whether it is enabled stays the admin's decision (Debian's rule, as -# dh_installsystemd does it): the postrm masks the unit while the package is removed, a -# reinstall restores the decision, a purge forgets it. Up to 7.3.6 remove also disabled it. +# dh_installsystemd does it): a reinstall after a remove restores it, a purge forgets it +# (postrm). Up to 7.3.6 remove also disabled it. if [ "$1" = remove ] && [ -d /run/systemd/system ]; then deb-systemd-invoke stop linuxmuster-squid.service >/dev/null || true fi diff --git a/docs/operations.md b/docs/operations.md index 866e8b9..6655854 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -152,10 +152,10 @@ and starts `linuxmuster-squid`; after that, upgrades keep what the admin decided - **Disabled but still running** (`systemctl disable` without `--now`): an upgrade restarts it with the new code and updates the instances; it stays disabled. - **Masked** (`systemctl mask`): the package leaves it alone. -- **Remove and reinstall:** `apt remove` only stops the service and keeps the decision (the unit - stays masked while the package is removed); a reinstall brings it back as it was. `apt purge` - forgets it: the next installation is a new one, enabled and started. A reinstall after removing - 7.3.6 or older is a new installation as well (those versions disabled the service on remove). +- **Remove and reinstall:** `apt remove` only stops the service and keeps the decision; a + reinstall brings it back as it was. `apt purge` forgets it: the next installation is a new one, + enabled and started. A reinstall after removing 7.3.6 or older is a new installation as well + (those versions disabled the service on remove). - **The first upgrade to 7.3.7** takes over the state the service is in at that moment, once: 7.3.6 and older kept no record of it. Enabled stays enabled, disabled or masked stays so. - **Downgrade to 7.3.6 or older:** that version enables and starts the service again. From 23cb5846109819809a95912d3929fc252d74326e Mon Sep 17 00:00:00 2001 From: Kevin Stenzel Date: Mon, 28 Sep 2026 17:37:05 +0200 Subject: [PATCH 5/5] docs(changelog): name the offline exception of the transition; update-all ran on upgrades From the cold verification (linuxmusterDEV work/verification/ cold-dienst-zustand.md), text only: * The changelog and docs/operations.md now state the one exception the preinst makes on purpose: when the one-time transition runs without a running systemd (an image build, a chroot), a service without an enable link is treated like a new installation and enabled, because the old postinst never enabled it offline. Online upgrades and every later upgrade keep an admin-disabled service off. * The old postinst updated the instances on every upgrade (and on dpkg-reconfigure, which also passes the installed version), never on a new installation; the changelog no longer says "every install". The entry keeps its version and date. --- debian/changelog | 10 ++++++++-- docs/operations.md | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8dd7d8e..0a095d3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,8 +2,9 @@ linuxmuster-squid (7.3.7) lmn73; urgency=medium * Upgrades respect a service the admin switched off. Up to 7.3.6 every install, upgrade and dpkg-reconfigure enabled and started - linuxmuster-squid.service again, and updated the proxy instances - through it (lmnsquid update-all, reconcile). + linuxmuster-squid.service again, and every upgrade (and + dpkg-reconfigure) updated the proxy instances through it + (lmnsquid update-all, reconcile). The package now follows Debian's rules (deb-systemd-helper and deb-systemd-invoke, as packages built with dh_installsystemd do): a new installation enables and starts the service; after that, upgrades keep @@ -17,6 +18,11 @@ linuxmuster-squid (7.3.7) lmn73; urgency=medium * The first upgrade to 7.3.7 takes over the state the service is in at that moment (the new preinst records it once), because 7.3.6 and older kept no record of it: a service that is disabled then stays disabled. + One exception: if this first upgrade runs without a running systemd + (an image build, a chroot), a service without an enable link is + treated like a new installation and enabled, because 7.3.6 and older + never enabled it offline. Upgrades with systemd running, and every + later upgrade, keep a service the admin disabled off. * Removing the package now only stops the service; it is no longer disabled. The decision is kept while the package is removed and applies again when it is reinstalled; purge forgets it. Reinstalling after diff --git a/docs/operations.md b/docs/operations.md index 6655854..49a2cfb 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -158,6 +158,10 @@ and starts `linuxmuster-squid`; after that, upgrades keep what the admin decided (those versions disabled the service on remove). - **The first upgrade to 7.3.7** takes over the state the service is in at that moment, once: 7.3.6 and older kept no record of it. Enabled stays enabled, disabled or masked stays so. + One exception: if this first upgrade runs without a running systemd (an image build, a + chroot), a service without an enable link is treated like a new installation and enabled, + because 7.3.6 and older never enabled it offline. Upgrades with systemd running, and every + later upgrade, keep a service the admin disabled off. - **Downgrade to 7.3.6 or older:** that version enables and starts the service again. - **Without a running systemd** (an image build, a chroot) a new installation only enables the service; it starts with the next boot.