aboutsummaryrefslogtreecommitdiffstats
path: root/docs/specs/2026-07-13-image-builder-design.md
blob: 9ee1a110fea116bad2b5555cd81a6f69596b6b5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# sbo-testbuild image builder — design

Date: 2026-07-13
Status: approved (design)

## Purpose

Produce and serve the docker images the `.extras/test-build` tool consumes.
`.extras/test-build` verifies a published SBo package still builds on a target
Slackware version inside a throwaway container; it does **not** build the
images, it only checks image presence by tag and errors if absent. This is the
missing image-builder job.

Images produced (x86_64 only for now):

- `sbo-testbuild:current` — Slackware -current, full install + sbopkg +
  sbo-maintainer-tools
- `sbo-testbuild:15.0` — Slackware 15.0, same

Served from a LAN docker registry so `.extras/test-build`'s `require_image`
can `docker pull` them, and so `installed_in_base` can later query the image's
package db.

## Infrastructure

Self-hosted on a Slackware VM (proxmox node), fully offline from the public
internet for the Slackware bits:

- **Host**: `docker.noland.dnx` (LAN DNS), Slackware x86_64, 4 vCPU / 4 GB RAM
  / 80 GB disk.
- **NAS trees**: two standard Slackware mirror trees NFS-mounted read-only:
  - `/mnt/nas/slackware64-current`
  - `/mnt/nas/slackware64-15.0`
  Each is a full mirror tree (`PACKAGES.TXT`, `ChangeLog.txt`, `slackware64/`
  series dirs, `patches/`, `extra/`).
- **Registry**: `registry:2` container on the VM, port 5000, plain HTTP (LAN
  only). Pull path `docker.noland.dnx:5000/sbo-testbuild:{tag}`.
- **Local .txz drop**: `/opt/sbo-testbuild/pkgs/` holds `sbopkg-*.txz` and
  `sbo-maintainer-tools-*.txz`, provided by the user (sbopkg packaged from the
  user's SBo repo). Installed into the image, not built in it (hermetic, no
  build-time deps).

Both `docker.noland.dnx` and every client that pulls (this dev box, the
`buildsystem` VM) need the registry marked insecure in
`/etc/docker/daemon.json`:
`{"insecure-registries":["docker.noland.dnx:5000"]}`, then `docker` restarted.

i586 is out of scope for now; the variant list is written to extend to it
later without restructuring.

## Architecture

Three scripts, chained, adapted from the forge repo
`slackware/docker-images` (`scripts/bootstrap.sh`, `build-full-image.sh`,
`build-builder-image.sh`). The forge `build-builder-image.sh` is **not** used:
the full image already ships the toolchain (series d, kde, x, etc.), so a
separate builder layer is redundant here.

```
bootstrap.sh              FROM scratch; installpkg base pkgs read from NAS
  → sbo-base:{ver}          via file://; configure rootfs for container use.

build-full-image.sh       FROM sbo-base:{ver}; slackpkg install every series
  → sbo-full:{ver}          (a ap d e f k kde l n t tcl x xap xfce y).

build-sbo-testbuild.sh    FROM sbo-full:{ver}; installpkg sbopkg +
  → sbo-testbuild:{ver}     sbo-maintainer-tools from /opt/sbo-testbuild/pkgs.
```

All three push to `docker.noland.dnx:5000`.

### Namespacing

Forge scripts push to `registry.slackware.nl/slackware/slackware*`. Re-namespace
to the LAN registry with our own image names:

- `docker.noland.dnx:5000/sbo-base:{ver}`
- `docker.noland.dnx:5000/sbo-full:{ver}`
- `docker.noland.dnx:5000/sbo-testbuild:{ver}`

`sbo-base` and `sbo-full` are intermediate; `.extras/test-build` only pulls
`sbo-testbuild`.

## Configuration

Single shared file `config`, sourced by all scripts and the test:

```sh
REGISTRY=docker.noland.dnx:5000
MIRROR=file:///mnt/nas
VARIANTS=(current 15.0)          # x86_64 only for now
PKGDIR=/opt/sbo-testbuild/pkgs   # sbopkg + sbo-maintainer-tools .txz
```

The forge scripts hardcode `MIRROR=https://slackware.nl/slackware` and
`REGISTRY=registry.slackware.nl/...`; both move to the shared config.

### NAS path mapping

bootstrap derives the per-variant repo dir as `slackware64-${VERSION}` for
x86_64 (`DIRSUFFIX=64`). With `MIRROR=file:///mnt/nas` this resolves to
`file:///mnt/nas/slackware64-current/...` and `.../slackware64-15.0/...`,
matching the mount points. Mounts must be named to match.

## Patches to the forge scripts

1. **`file://`-aware fetch.** Forge downloads `PACKAGES.TXT` and packages with
   `curl` over http. Replace with one helper in `lib.sh` that detects a
   `file://` (or bare-local) MIRROR and uses `cp`, otherwise `curl`. curl's
   own `file://` support is unreliable across builds, so branch explicitly.
2. **Drop i586.** `ALL_VARIANTS` becomes the config `VARIANTS` (x86_64 only).
   All the `ARCH_SUFFIX`/`-i586` machinery collapses to the x86_64 path.
3. **Re-namespace + LAN registry** (see Namespacing).
4. **MIRROR → `file:///mnt/nas`** (see config).

## Data flow and rebuild gating

Nightly cron on the VM (root; `bootstrap` needs `installpkg`):

```cron
 0 4 * * *  bootstrap.sh
15 4 * * *  build-full-image.sh
30 4 * * *  build-sbo-testbuild.sh
```

Rebuild gating (kept from the forge scripts, extended):

- **bootstrap**: sha256 of the NAS `ChangeLog.txt` (read via `file://`)
  compared to a stored hash under `HASH_DIR`; skip the variant if unchanged.
- **build-full-image**: base image digest recorded as a label on the full
  image; pull base, compare, skip if unchanged.
- **build-sbo-testbuild**: full image digest label, same pattern; **plus** a
  hash of the `.txz` set in `PKGDIR`, so a sbopkg / sbo-maintainer-tools bump
  forces a rebuild even when the full image is unchanged.

Net effect: a quiet NAS tree makes the whole chain no-op in seconds; a
-current churn cascades base → full → testbuild.

### Local-registry digest consistency

The forge scripts read the base/full digest via `docker inspect RepoDigests`
after `docker pull`. Self-hosted, those intermediate images live in the LAN
registry, so pull-then-inspect must target `docker.noland.dnx:5000`. Since
every image name is derived from `REGISTRY`, re-namespacing handles this with
no extra logic.

## Error handling

- `set -euo pipefail` in every script (forge already has it).
- **Missing NAS mount**: guard `[[ -d /mnt/nas/slackware64-$ver ]]` first;
  `_err` if absent.
- **Missing `.txz`**: `build-sbo-testbuild` errors before building if
  `PKGDIR` lacks a `sbopkg-*.txz` or `sbo-maintainer-tools-*.txz` — never push
  a testbuild image missing sbopkg.
- **Registry unreachable**: `docker push` fails under `set -e`; the chain
  aborts. The base/full may already be built locally; cron retries next night.
  Acceptable.
- **Per-variant isolation**: one variant failing (e.g. current) must not block
  the other (15.0). Each variant runs wrapped; log the failure, continue, and
  exit non-zero at the end so cron logs surface it.

## Testing

Pure-logic pieces get a `test-image-builder.sh` self-check, matching the
existing `.extras/test-build` pattern (`.extras/test-logic.sh`, assert-based,
no framework). No docker daemon in tests — hermetic.

- **`file://` fetch helper**: local file resolves via cp; missing local file
  errors; an http MIRROR still routes to curl.
- **`.txz`-set hash**: stable across file reordering; changes when a file's
  content changes.
- **variant guard**: a missing mount dir yields non-zero.

The real end-to-end chain is verified by a manual first run on the VM (the user
runs it and reports back), the same division of labor as `.extras/test-build`
Task 10.

## Layout

```
.extras/image-builder/
├── config                    REGISTRY, MIRROR, VARIANTS, PKGDIR
├── lib.sh                    shared: _log/_warn/_err, fetch, txz_hash, guards
├── bootstrap.sh              base image (root; installpkg from NAS)
├── build-full-image.sh       full image (slackpkg all series)
├── build-sbo-testbuild.sh    testbuild image (installpkg sbopkg + tools)
├── test-image-builder.sh     pure-logic self-check
└── README                    VM-side setup, reproducible
```

Lives in `.extras/` for now (per the user), relocatable later.

The README carries the user-side checklist so it is reproducible and not
trapped in chat:

- VM specs (4/4/80), Slackware x86_64, docker installed.
- Two NFS mounts (ro), root-readable, named `slackware64-current` /
  `slackware64-15.0` under `/mnt/nas`.
- `registry:2` container: `-v /opt/registry/data:/var/lib/registry`,
  `--restart=always`, port 5000.
- `/etc/docker/daemon.json` insecure-registry entry on the VM **and** on every
  pulling client (this dev box, `buildsystem`), docker restarted.
- Drop `sbopkg-*.txz` and `sbo-maintainer-tools-*.txz` into
  `/opt/sbo-testbuild/pkgs/`.
- Install the three cron lines.
- VM static IP / LAN DNS for `docker.noland.dnx`.

## Out of scope

- i586 images (extensible later via `VARIANTS` / arch machinery).
- Building sbopkg / sbo-maintainer-tools from source in the image (installed
  from prebuilt `.txz`).
- The forge `build-builder-image.sh` toolchain layer (redundant with full).
- `installed_in_base` implementation in `.extras/test-build` — unblocked by
  this work (queries the built image's package db) but tracked separately.
- Registry auth / TLS (LAN-only plain HTTP).