Skip to content

Commit 483fa06

Browse files
6RUN0claude
andcommitted
feat!: rebrand unit→freeunit and parametrize brand identity
Upstream rebranded its packages and on-disk identity from unit* to freeunit* (daemon freeunitd, /var/lib/freeunit, control.freeunit.sock, the freeunit:freeunit user/group). Since this image only installs the prebuilt .deb artifacts, that identity is fixed by the artifact — so the download/verify/install names, the runtime paths, and the default app user/group all move to freeunit, and FREEUNIT_VERSION/FREEUNIT_RELEASE bump to 1.35.6-1 / 1.35.6-build2 with a recomputed SHA256SUMS anchor. To document intent without making identity a free knob, a brand ARG group (BRAND/RUNTIME/RUNDIR/BRAND_TITLE/HOMEPAGE/DOCS_URL) mirrors the upstream packaging vocabulary and feeds RUN/LABEL only; a build-time guard then asserts the installed ${RUNTIME}d's compiled paths match, so a future upstream identity change fails the build instead of shipping an image whose entrypoint manages the wrong paths. The default launch moves out of CMD into docker-entrypoint.sh and the HEALTHCHECK becomes docker-healthcheck.sh, because an exec-form CMD/HEALTHCHECK cannot expand a build ARG/ENV — keeping the binary name and control-socket path single-sourced from the entrypoint library rather than duplicated in the Dockerfile. BREAKING CHANGE: config snippets must set "user"/"group": "freeunit" (was "unit"); the state dir is /var/lib/freeunit and the control socket control.freeunit.sock; APPLICATION_USER/APPLICATION_GROUP default to freeunit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0130607 commit 483fa06

29 files changed

Lines changed: 309 additions & 140 deletions

.github/workflows/check-upstream.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,19 @@ jobs:
8080
8181
# New deb version comes from the core asset name. GitHub renames the
8282
# '~' to '.' in download URLs, but SHA256SUMS keeps the real '~' name:
83-
# unit_<version>~<suite>_amd64.deb
84-
core=$(grep -oE "unit_[^ ]+~${suite}_amd64\.deb" "$work/SHA256SUMS" | head -n1)
85-
: "${core:?no core unit_*~${suite}_amd64.deb entry in SHA256SUMS}"
86-
new_version=${core#unit_}
83+
# freeunit_<version>~<suite>_amd64.deb
84+
# The '_' right after the brand isolates the core from the hyphenated
85+
# siblings (freeunit-dev_, freeunit-php8.3_, freeunit-dbg_, ...).
86+
core=$(grep -oE "freeunit_[^ ]+~${suite}_amd64\.deb" "$work/SHA256SUMS" | head -n1)
87+
: "${core:?no core freeunit_*~${suite}_amd64.deb entry in SHA256SUMS}"
88+
new_version=${core#freeunit_}
8789
new_version=${new_version%"~${suite}_amd64.deb"}
8890
: "${new_version:?could not parse the deb version from $core}"
8991
9092
# Refuse to bump to an incomplete release: every matrixed PHP line must
9193
# have its module .deb checksummed, or the build would fail later.
9294
for php in $PHP_LINES; do
93-
entry="unit-php${php}_${new_version}~${suite}_amd64.deb"
95+
entry="freeunit-php${php}_${new_version}~${suite}_amd64.deb"
9496
grep -qF "$entry" "$work/SHA256SUMS" \
9597
|| { echo "ERROR: release $new_release has no entry for $entry" >&2; exit 1; }
9698
done

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ release records the FreeUnit and PHP versions it ships.
1010

1111
## [Unreleased]
1212

13+
### Changed
14+
15+
- **FreeUnit rebrand (breaking for downstream images).** Upstream renamed its
16+
Debian packages and on-disk identity from `unit*` to `freeunit*`, and the image
17+
follows suit: the daemon is `freeunitd`/`freeunitd-debug`, the state dir
18+
`/var/lib/freeunit`, the control socket `/var/run/control.freeunit.sock`, the log
19+
`/var/log/freeunit.log`, and the default application user/group `freeunit` (was
20+
`unit`). Downstream images must move any `config.json` `user`/`group` keys and
21+
any custom launch command from `unit`/`unitd` to `freeunit`/`freeunitd`; the
22+
reusable entrypoint library keeps its function names and `UNIT_*`/`APPLICATION_*`
23+
variable identifiers, so hooks that only call the documented API are unaffected.
24+
- Bumped the bundled FreeUnit `.deb` release to `1.35.6-build2` (`1.35.6-1`) and
25+
recomputed the `FREEUNIT_SHA256SUMS_SHA256` trust anchor.
26+
- The brand identity is single-sourced as Dockerfile `ARG`s mirroring the upstream
27+
packaging vocabulary (`BRAND`, `RUNTIME`, `RUNDIR`, `BRAND_TITLE`, `HOMEPAGE`,
28+
`DOCS_URL`). They default to `freeunit`/`/var/run` and the build now **asserts the
29+
installed binary's compiled paths match `RUNTIME`/`RUNDIR`**, so a future upstream
30+
identity change fails the build instead of shipping an image whose entrypoint
31+
manages paths the daemon does not use.
32+
- The default launch command moved out of the Dockerfile `CMD` into the entrypoint,
33+
and the `HEALTHCHECK` is now a script (`/docker-healthcheck.sh`), so the binary
34+
name and control-socket path stay single-sourced from `docker-entrypoint-common.sh`
35+
instead of being duplicated in an exec-form instruction that cannot expand a
36+
build ARG.
37+
1338
## [0.0.5] - 2026-06-09
1439

1540
### Added

CHANGELOG.ru.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@
1010

1111
## [Не выпущено]
1212

13+
### Изменено
14+
15+
- **Ребрендинг FreeUnit (ломающее изменение для дочерних образов).** Апстрим
16+
переименовал Debian-пакеты и идентичность на диске с `unit*` на `freeunit*`, и
17+
образ следует за ним: демон — `freeunitd`/`freeunitd-debug`, каталог состояния —
18+
`/var/lib/freeunit`, управляющий сокет — `/var/run/control.freeunit.sock`, лог —
19+
`/var/log/freeunit.log`, пользователь/группа приложения по умолчанию — `freeunit`
20+
(было `unit`). Дочерние образы должны заменить ключи `user`/`group` в
21+
`config.json` и любую кастомную команду запуска с `unit`/`unitd` на
22+
`freeunit`/`freeunitd`; библиотека entrypoint сохраняет имена функций и
23+
идентификаторы переменных `UNIT_*`/`APPLICATION_*`, поэтому хуки, использующие
24+
только документированный API, не затронуты.
25+
- Версия встроенного релиза FreeUnit `.deb` поднята до `1.35.6-build2` (`1.35.6-1`),
26+
пересчитан якорь доверия `FREEUNIT_SHA256SUMS_SHA256`.
27+
- Идентичность бренда вынесена в единый источник — `ARG` в `Dockerfile`,
28+
повторяющие словарь апстрим-упаковки (`BRAND`, `RUNTIME`, `RUNDIR`, `BRAND_TITLE`,
29+
`HOMEPAGE`, `DOCS_URL`). По умолчанию `freeunit`/`/var/run`, и сборка теперь
30+
**проверяет, что вкомпилированные пути установленного бинаря совпадают с
31+
`RUNTIME`/`RUNDIR`**, поэтому смена идентичности в апстриме уронит сборку, а не
32+
выпустит образ, чей entrypoint управляет не теми путями.
33+
- Команда запуска по умолчанию перенесена из `CMD` в `Dockerfile` в entrypoint, а
34+
`HEALTHCHECK` стал скриптом (`/docker-healthcheck.sh`), чтобы имя бинаря и путь
35+
управляющего сокета оставались в едином источнике (`docker-entrypoint-common.sh`),
36+
а не дублировались в инструкции exec-формы, которая не раскрывает build ARG.
37+
1338
## [0.0.5] - 2026-06-09
1439

1540
### Добавлено

CLAUDE.md

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ embedded PHP language module on Debian **trixie** (amd64). The whole
2323
php-version / suite / FreeUnit-release matrix is covered by build args — there
2424
is no code generation and no committed per-variant directories.
2525

26-
FreeUnit ships **prebuilt `.deb` packages** (core `unit` + per-version
27-
`unit-phpX.Y` modules) on its GitHub releases page, so the build installs them
26+
FreeUnit ships **prebuilt `.deb` packages** (core `freeunit` + per-version
27+
`freeunit-phpX.Y` modules) on its GitHub releases page, so the build installs them
2828
directly — it does **not** compile Unit or the PHP module from source.
2929

30+
Upstream rebranded its packages and on-disk identity from `unit*` to `freeunit*`
31+
(daemon `freeunitd`, `/var/lib/freeunit`, `control.freeunit.sock`, the
32+
`freeunit:freeunit` user/group). Because this image only *installs* the prebuilt
33+
`.deb`s, that identity is fixed by the artifact, not chosen here — see the brand
34+
ARGs and the build-time guard below.
35+
3036
## Build matrix
3137

3238
The `Makefile` drives the matrix; each target is one `docker build` with
@@ -41,15 +47,25 @@ docker build --build-arg PHP_VER=8.3 -t x . # one-off without make
4147
```
4248

4349
Key build args (defaults in the `Dockerfile`): `PHP_VER` (8.4), `SUITE` (trixie),
44-
`IMAGE_SUFFIX` (-slim), `FREEUNIT_VERSION` (deb version base, e.g. `1.35.5-1`),
45-
`FREEUNIT_RELEASE` (GitHub release tag, e.g. `1.35.5-build4`), and the
50+
`IMAGE_SUFFIX` (-slim), `FREEUNIT_VERSION` (deb version base, e.g. `1.35.6-1`),
51+
`FREEUNIT_RELEASE` (GitHub release tag, e.g. `1.35.6-build2`), and the
4652
`DEBIAN_MIRROR` / `DEBIAN_SECURITY_MIRROR` / `SURY_MIRROR` mirror args (neutral
4753
CDN defaults; sury is fetched over https because its signing key travels that
4854
channel). The `Makefile` reads `SUITE` / `PHP_VER` / `FREEUNIT_VERSION` /
4955
`FREEUNIT_RELEASE` defaults out of the `Dockerfile` ARGs via `sed`, so they are
5056
**single-sourced**: bumping a FreeUnit release = edit `FREEUNIT_VERSION` +
5157
`FREEUNIT_RELEASE` in the `Dockerfile` only (or pass on the command line).
5258

59+
A second ARG group single-sources the **brand identity**, mirroring the upstream
60+
packaging vocabulary (`freeunit/pkg/deb/Makefile`): `BRAND` (dpkg/apt asset and
61+
package names we download), `RUNTIME` (the on-disk identity compiled into the
62+
package — binary `${RUNTIME}d`, `/var/lib/${RUNTIME}`, `control.${RUNTIME}.sock`,
63+
`/var/log/${RUNTIME}.log`, the `${RUNTIME}:${RUNTIME}` user/group), `RUNDIR` (the
64+
socket + pid dir), plus `BRAND_TITLE` / `HOMEPAGE` / `DOCS_URL` for the OCI labels.
65+
All default to `freeunit` / `/var/run`. These are **not** free knobs (the prebuilt
66+
`.deb`s carry one fixed identity); they document intent and are asserted against
67+
the installed binary at build time (see below).
68+
5369
The Makefile's immutable image tag is `$(SUITE)-$(FREEUNIT_RELEASE)-php$*` (the
5470
release is the unique build id), plus the floating `$(SUITE)-php$*`.
5571

@@ -65,25 +81,34 @@ release is the unique build id), plus the floating `$(SUITE)-php$*`.
6581
3. **php_image** also installs the fragile extensions (`imagick`,
6682
`uploadprogress`) in a **best-effort** loop (`apt-get install … || echo
6783
WARNING`), so a missing extension on a newer PHP line does not fail the build.
68-
4. **final_image** — downloads `unit_*` and `unit-php${PHP_VER}_*` `.deb`s from the
69-
FreeUnit GitHub release, verifies `SHA256SUMS` itself against the pinned
84+
4. **final_image** — downloads `${BRAND}_*` and `${BRAND}-php${PHP_VER}_*` `.deb`s
85+
from the FreeUnit GitHub release, verifies `SHA256SUMS` itself against the pinned
7086
`FREEUNIT_SHA256SUMS_SHA256` trust anchor (see below), then verifies the `.deb`s
7187
against it (asserting both expected entries are present with `grep -qF`, so
7288
`--ignore-missing` cannot pass vacuously), and `apt-get install`s the two local
7389
files (apt pulls
74-
`libphp${PHP_VER}-embed` already present and the virtual `unit-rX.Y.Z` provided
75-
by the core package). Then it prepares `/var/lib/unit` + `/docker-entrypoint.d`,
76-
symlinks `unit.log` to stdout, `COPY rootfs/ /`, sets OCI `LABEL`s, and sets the
77-
`tini` entrypoint + `unitd --no-daemon`.
90+
`libphp${PHP_VER}-embed` already present and the virtual `${RUNTIME}-rX.Y.Z`
91+
provided by the core package). Then it prepares `/var/lib/${RUNTIME}` +
92+
`/docker-entrypoint.d`, symlinks `/var/log/${RUNTIME}.log` to stdout, **asserts
93+
the installed `${RUNTIME}d`'s compiled paths (`--statedir`/`--control`/`--pid`/
94+
`--log`/`--user`/`--group` from its `--version` configure line) match
95+
`RUNTIME`/`RUNDIR`** — so a future upstream identity change fails the build
96+
instead of shipping an image whose entrypoint manages the wrong paths — then
97+
`COPY rootfs/ /` and sets OCI `LABEL`s. There is **no `CMD`**: the default launch
98+
(`${RUNTIME}d --no-daemon --control …`) lives in the entrypoint and the
99+
`HEALTHCHECK` is a script (`/docker-healthcheck.sh`), so the binary name and
100+
socket path stay single-sourced from `docker-entrypoint-common.sh` rather than
101+
duplicated in an exec-form instruction (which cannot expand a build ARG/ENV).
102+
`tini` is the entrypoint wrapper.
78103

79104
`SUITE` and `IMAGE_SUFFIX` are declared **before the first `FROM`** so they reach
80105
the `FROM` line; they are re-declared inside stages that use them in `RUN`.
81106

82107
### Critical packaging detail (asset name vs deb version)
83108

84109
GitHub renames the `~` in deb version strings to `.` in asset file names, so the
85-
**same** package is downloaded as `unit_1.35.5-1.trixie_amd64.deb` but its real
86-
version (and its `SHA256SUMS` entry) is `1.35.5-1~trixie`. The Dockerfile downloads
110+
**same** package is downloaded as `freeunit_1.35.6-1.trixie_amd64.deb` but its real
111+
version (and its `SHA256SUMS` entry) is `1.35.6-1~trixie`. The Dockerfile downloads
87112
with `curl -o` into the **`~` name** so `sha256sum -c --ignore-missing` actually
88113
matches the file. Saving under the `.` name would make the checksum check pass
89114
**vacuously** (no matching filename → entry skipped) — a silent integrity hole.
@@ -96,8 +121,8 @@ trusted `SHA256SUMS` **in version control**, and the build aborts unless the
96121
fetched file hashes to it. Bumping FreeUnit therefore means recomputing this
97122
digest too — which is exactly what `check-upstream.yml` automates.
98123

99-
The core `unit` package's `postinst` creates the `unit:unit` system user/group,
100-
so the Dockerfile does not create it manually.
124+
The core `freeunit` package's `postinst` creates the `freeunit:freeunit` system
125+
user/group, so the Dockerfile does not create it manually.
101126

102127
## rootfs overlay
103128

@@ -110,12 +135,15 @@ lives there at its target path:
110135
library): if a sourced file defined a shell function `handle_<cmd>` matching
111136
`$1`, that handler owns the launch (it must `exec`, and a return or a non-zero
112137
exit is a fatal contract violation);
113-
otherwise, for `unitd`/`unitd-debug` it runs the standard first-run routine
114-
(`unit_initial_configuration`) and `exec`s the command. The first-run routine
115-
only fires when `/var/lib/unit` is empty: it launches unitd against the control
116-
socket and applies everything in `/docker-entrypoint.d/` (`*.sh` executed,
138+
otherwise, for `freeunitd`/`freeunitd-debug` it runs the standard first-run
139+
routine (`unit_initial_configuration`) and `exec`s the command. (When invoked
140+
with no command at all, the dispatcher first supplies the default launch —
141+
`${UNIT_BINARY} --no-daemon --control …` — from the `UNIT_*` constants, so the
142+
image needs no `CMD`.) The first-run routine
143+
only fires when `/var/lib/freeunit` is empty: it launches freeunitd against the
144+
control socket and applies everything in `/docker-entrypoint.d/` (`*.sh` executed,
117145
`*.pem` uploaded as certificate bundles, `*.json` PUT to `config`), then stops
118-
unitd so the real start boots a populated state.
146+
freeunitd so the real start boots a populated state.
119147
- `rootfs/docker-entrypoint-common.sh` — the reusable **core library** sourced by
120148
the entrypoint (and available to child hooks). Every function takes its target
121149
as an argument, defaulting to the `UNIT_*` / `ENTRYPOINT_*` constants, so a
@@ -144,8 +172,14 @@ lives there at its target path:
144172
idempotent; a child hook calls it to create a *different* app user.
145173
- `unit_initial_configuration <binary>` is the full first-run routine composed
146174
from the above, guarded by its `unit_config_failed` EXIT trap.
147-
On source it provisions the default app user from `APPLICATION_USER/UID/GROUP/
148-
GID/DIR/CHOWN` by calling `setup_user` with them.
175+
On source it provisions the default app user (default `freeunit`, which maps to
176+
the package's system `freeunit:freeunit`) from `APPLICATION_USER/UID/GROUP/
177+
GID/DIR/CHOWN` by calling `setup_user` — unless `UNIT_LIB_NO_PROVISION=1` is set,
178+
which yields the constants/functions without the side effect (used by
179+
`/docker-healthcheck.sh`, which sources the lib only to read `UNIT_CONTROL_SOCKET`).
180+
The path constants derive from `UNIT_RUNTIME` (`freeunit`) and `UNIT_RUNDIR`
181+
(`/var/run`), readonly literals — deliberately not env-derived — so the state-dir
182+
wipe target and control paths cannot be redirected at runtime.
149183

150184
`/docker-entrypoint.d` itself is created by the `RUN` (empty dirs are not tracked
151185
in git); users mount or add config snippets into it at runtime.
@@ -179,8 +213,9 @@ The two reusable routines above are the public surface handlers build on.
179213

180214
## Verification
181215

182-
- **Build** — the `final_image` stage runs `unitd --version` and `php -v`, so a
183-
successful build proves the daemon and module load.
216+
- **Build** — the `final_image` stage runs `freeunitd --version` (also asserting
217+
its compiled paths match `RUNTIME`/`RUNDIR`) and `php -v`, so a successful build
218+
proves the daemon and module load and the image's paths match the binary.
184219
- **Smoke test**`test/smoke.sh <image-ref>` runs the image with the
185220
`test/fixtures/` PHP app mounted (config into `/docker-entrypoint.d`, code into
186221
`/www`) and asserts a request is served by PHP — the entrypoint's happy path.
@@ -237,4 +272,4 @@ The two reusable routines above are the public surface handlers build on.
237272
the build context.
238273
- The entrypoint uses bash globs (`shopt -s nullglob globstar`) over
239274
`/docker-entrypoint.d`, not `for f in $(find …)`; it passes `--pid` explicitly
240-
and wipes `/var/lib/unit` if first-run config fails (retry on next start).
275+
and wipes `/var/lib/freeunit` if first-run config fails (retry on next start).

0 commit comments

Comments
 (0)