Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
40 changes: 39 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ does not yet exist — every `lmnsquid create --image ghcr.io/…@sha256:<digest
workflow refuses a tag that does not match it. Build locally with `make deb`
(dpkg-buildpackage, which needs no root; in the same container, started as root for
`apt-get build-dep`: the command is in the `Makefile`). An
`apt install` of the new `.deb` **automatically restarts the service** (postinst `try-restart`),
`apt install` of the new `.deb` **automatically restarts the service** (postinst
`deb-systemd-invoke restart`; a service the admin disabled only while it still runs),
so that the new code is actually loaded (E2E-verified via `deb_smoke.sh`). Distribution:
the GitHub release is the only channel for now. ⏸ An apt archive of our own
(`lmndeb.fair-comp.de`, decided by Kevin) comes later; apt verifies the signed repository
Expand Down
39 changes: 39 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
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 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
the admin's decision. Enabled: the service is restarted with the new
code, and started if it was stopped. Disabled ("systemctl disable
--now linuxmuster-squid"): it stays disabled and stopped, the
instances are not updated, and apt shows one line saying so; run
"lmnsquid update-all" after starting the service. Disabled but still
running: restarted with the new code, not enabled. Masked: left alone.
docs/operations.md explains how to keep the service off.
* 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
removing 7.3.6 or older enables and starts the service like a new
installation.
* Installed without a running systemd (an image build, a chroot), the
service is now enabled and starts with the next boot; before, it was not
enabled at all. Where /usr/sbin/policy-rc.d forbids starting services
(build containers), the package no longer starts or restarts it.
* A downgrade to 7.3.6 or older brings the old behaviour back: that
version enables and starts the service again on its install.

-- Kevin Stenzel <mail@kevin-stenzel.de> 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
Expand Down
50 changes: 38 additions & 12 deletions debian/postinst
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,47 @@ 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
# 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
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
Expand Down
5 changes: 5 additions & 0 deletions debian/postrm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ if [ -d /run/systemd/system ]; then
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
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.
Expand Down
27 changes: 27 additions & 0 deletions debian/preinst
Original file line number Diff line number Diff line change
@@ -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 <version>") 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
10 changes: 6 additions & 4 deletions debian/prerm
Original file line number Diff line number Diff line change
Expand Up @@ -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): 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
systemctl disable --now linuxmuster-squid.service || true
deb-systemd-invoke stop linuxmuster-squid.service >/dev/null || true
fi
#DEBHELPER#
exit 0
3 changes: 2 additions & 1 deletion debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -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:
@:

Expand Down
10 changes: 7 additions & 3 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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_<new>_amd64.deb # restarts the service, then update-all
apt-get install -y ./linuxmuster-squid_<new>_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
Expand Down
Loading
Loading